tx2uml
Version:
Ethereum transaction visualizer that generates UML sequence diagrams.
173 lines • 7.24 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = __importDefault(require("axios"));
const ethers_1 = require("ethers");
const tx2umlTypes_1 = require("../types/tx2umlTypes");
const regEx_1 = require("../utils/regEx");
const utils_1 = require("ethers/lib/utils");
const EthereumNodeClient_1 = __importDefault(require("./EthereumNodeClient"));
require("axios-debug-log");
const debug = require("debug")("tx2uml");
class OpenEthereumClient extends EthereumNodeClient_1.default {
constructor(url, network) {
super(url, network);
this.url = url;
this.jsonRpcId = 0;
}
async getTransactionTrace(txHash) {
if (!txHash?.match(regEx_1.transactionHash)) {
throw new TypeError(`Transaction hash "${txHash}" must be 32 bytes in hexadecimal format with a 0x prefix`);
}
try {
debug(`About to get transaction trace for ${txHash}`);
const response = await axios_1.default.post(this.url, {
id: this.jsonRpcId++,
jsonrpc: "2.0",
method: "trace_transaction",
params: [txHash],
});
if (response.data?.error?.message) {
throw new Error(response.data.error.message);
}
if (!(response?.data?.result?.length > 0)) {
throw new Error(`no transaction trace messages in response: ${response?.data}`);
}
const traceResponses = response.data.result;
const parentTraces = [];
const traces = [];
let id = 0;
for (const trace of traceResponses) {
const traceDepth = trace.traceAddress.length;
const parentTrace = traceDepth > 0 ? parentTraces[traceDepth - 1] : undefined;
const type = convertType(trace);
const from = type === tx2umlTypes_1.MessageType.Selfdestruct
? trace.action?.address
: trace.action?.from;
const delegatedFrom = parentTrace?.type === tx2umlTypes_1.MessageType.DelegateCall
? parentTrace.to
: from;
const to = (() => {
switch (type) {
case tx2umlTypes_1.MessageType.Create:
return trace.result?.address;
case tx2umlTypes_1.MessageType.Selfdestruct:
return trace.action?.refundAddress;
default:
return trace.action?.to;
}
})();
const inputs = type === tx2umlTypes_1.MessageType.Create
? trace.action?.init
: trace.action?.input;
const outputs = type === tx2umlTypes_1.MessageType.Create
? trace.result?.code
: trace.result?.output;
const newTrace = {
id: id++,
type,
from,
delegatedFrom,
to,
value: trace.action?.value
? convertBigNumber(trace.action?.value)
: ethers_1.BigNumber.from(0),
inputs,
inputParams: [], // Will init later once we have the contract ABI
funcSelector: trace.action.input?.length >= 10
? trace.action.input.slice(0, 10)
: undefined,
outputs,
outputParams: [], // Will init later once we have the contract ABI
gasLimit: convertBigNumber(trace?.action.gas),
gasUsed: convertBigNumber(trace.result?.gasUsed),
parentTrace,
childTraces: [],
depth: traceDepth,
error: trace.error,
};
if (parentTrace) {
parentTrace.childTraces.push(newTrace);
}
// update the latest parent trace for this trace depth
if (!parentTraces[traceDepth]) {
parentTraces.push(newTrace);
}
else {
parentTraces[traceDepth] = newTrace;
}
traces.push(newTrace);
}
debug(`Got ${traces.length} traces actions for tx hash ${txHash} from ${this.url}`);
return traces;
}
catch (err) {
const error = new Error(`Failed to get transaction trace for tx hash ${txHash} from url ${this.url}.`, { cause: err });
throw error;
}
}
async getTransactionError(tx) {
if (!tx?.hash.match(regEx_1.transactionHash)) {
throw TypeError(`There is no transaction hash on the receipt object`);
}
if (tx.status) {
return undefined;
}
if (tx.gasUsed === tx.gasLimit) {
throw Error("Transaction failed as it ran out of gas.");
}
try {
debug(`About to get transaction trace for ${tx.hash}`);
const params = [
{
// A Nethermind bug means the nonce of the original transaction can be used.
// error.data: "wrong transaction nonce"
// nonce: tx.nonce,
gasPrice: tx.gasPrice.toHexString(),
gas: tx.gasLimit.toHexString(),
value: tx.value.toHexString(),
from: tx.from,
to: tx.to,
data: tx.data,
},
(0, utils_1.hexlify)(tx.blockNumber),
];
const response = await axios_1.default.post(this.url, {
id: this.jsonRpcId++,
jsonrpc: "2.0",
method: "eth_call",
params,
});
return response.data?.error?.data;
}
catch (err) {
throw new Error(`Failed to get transaction trace for tx hash ${tx.hash} from url ${this.url}.`, { cause: err });
}
}
}
exports.default = OpenEthereumClient;
const convertType = (trace) => {
let type = tx2umlTypes_1.MessageType.Call;
if (trace.action.callType === "delegatecall") {
return tx2umlTypes_1.MessageType.DelegateCall;
}
if (trace.action.callType === "staticcall") {
return tx2umlTypes_1.MessageType.StaticCall;
}
if (trace.type === "create") {
return tx2umlTypes_1.MessageType.Create;
}
else if (trace.type === "suicide") {
return tx2umlTypes_1.MessageType.Selfdestruct;
}
return type;
};
// convert an integer value to a decimal value. eg wei to Ethers which is to 18 decimal places
const convertBigNumber = (value) => {
if (!value)
return undefined;
return ethers_1.BigNumber.from(value);
};
//# sourceMappingURL=OpenEthereumClient.js.map