@layr-labs/hourglass-performer
Version:
TypeScript SDK for building Hourglass AVS performers
281 lines • 9.34 kB
JavaScript
;
// ABI encoding/decoding utilities for Solidity integration
Object.defineProperty(exports, "__esModule", { value: true });
exports.PayloadAutoDecoder = exports.SolidityTypeUtils = exports.AbiCodec = void 0;
const ethers_1 = require("ethers");
const protobuf_1 = require("../types/protobuf");
/**
* ABI codec for encoding/decoding Solidity function calls
*/
class AbiCodec {
constructor(abi) {
this.functionInfo = new Map();
this.abiCoder = ethers_1.AbiCoder.defaultAbiCoder();
this.contractInterface = new ethers_1.Interface(abi);
this.buildFunctionInfo();
}
/**
* Build function information from ABI
*/
buildFunctionInfo() {
for (const fragment of this.contractInterface.fragments) {
if (fragment.type === 'function') {
const funcFragment = fragment;
const info = {
name: funcFragment.name,
signature: funcFragment.format('minimal'),
inputs: funcFragment.inputs,
outputs: funcFragment.outputs,
selector: funcFragment.selector,
};
this.functionInfo.set(funcFragment.name, info);
}
}
}
/**
* Get function information by name
*/
getFunctionInfo(functionName) {
return this.functionInfo.get(functionName);
}
/**
* Get all available functions
*/
getAllFunctions() {
return Array.from(this.functionInfo.values());
}
/**
* Decode function call data
*/
decodeFunctionCall(functionName, data) {
const funcInfo = this.functionInfo.get(functionName);
if (!funcInfo) {
throw new Error(`Function ${functionName} not found in ABI`);
}
try {
// Convert bytes to hex string for ethers
const hexData = (0, protobuf_1.bytesToHex)(data);
// Decode using contract interface
const result = this.contractInterface.decodeFunctionData(functionName, hexData);
// Convert result to plain object with parameter names
const decoded = {};
for (let i = 0; i < funcInfo.inputs.length; i++) {
const param = funcInfo.inputs[i];
decoded[param?.name || `param${i}`] = result[i];
}
return decoded;
}
catch (error) {
throw new Error(`Failed to decode function call: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Encode function call data
*/
encodeFunctionCall(functionName, params) {
const funcInfo = this.functionInfo.get(functionName);
if (!funcInfo) {
throw new Error(`Function ${functionName} not found in ABI`);
}
try {
const encoded = this.contractInterface.encodeFunctionData(functionName, params);
return (0, protobuf_1.hexToBytes)(encoded);
}
catch (error) {
throw new Error(`Failed to encode function call: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Decode function result
*/
decodeFunctionResult(functionName, data) {
const funcInfo = this.functionInfo.get(functionName);
if (!funcInfo) {
throw new Error(`Function ${functionName} not found in ABI`);
}
try {
const hexData = (0, protobuf_1.bytesToHex)(data);
const result = this.contractInterface.decodeFunctionResult(functionName, hexData);
// Convert result to plain object with parameter names
if (funcInfo.outputs.length === 1) {
return result[0];
}
else {
const decoded = {};
for (let i = 0; i < funcInfo.outputs.length; i++) {
const param = funcInfo.outputs[i];
decoded[param?.name || `result${i}`] = result[i];
}
return decoded;
}
}
catch (error) {
throw new Error(`Failed to decode function result: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Encode function result
*/
encodeFunctionResult(functionName, result) {
const funcInfo = this.functionInfo.get(functionName);
if (!funcInfo) {
throw new Error(`Function ${functionName} not found in ABI`);
}
try {
// Handle single vs multiple return values
let values;
if (funcInfo.outputs.length === 1) {
values = [result];
}
else {
values = [];
for (let i = 0; i < funcInfo.outputs.length; i++) {
const param = funcInfo.outputs[i];
const key = param?.name || `result${i}`;
values.push(result[key]);
}
}
const encoded = this.abiCoder.encode(funcInfo.outputs.map(p => p.format()), values);
return (0, protobuf_1.hexToBytes)(encoded);
}
catch (error) {
throw new Error(`Failed to encode function result: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Detect if data looks like ABI-encoded function call
*/
static isAbiEncoded(data) {
// ABI-encoded function calls start with 4-byte function selector
return data.length >= 4;
}
/**
* Try to detect function from encoded data
*/
detectFunction(data) {
if (data.length < 4)
return null;
const selector = (0, protobuf_1.bytesToHex)(data.slice(0, 4));
for (const [name, info] of this.functionInfo) {
if (info.selector === selector) {
return name;
}
}
return null;
}
}
exports.AbiCodec = AbiCodec;
/**
* Utility functions for common Solidity types
*/
class SolidityTypeUtils {
/**
* Convert JavaScript value to Solidity type
*/
static toSolidityType(value, solidityType) {
switch (solidityType) {
case 'address':
return value.toLowerCase();
case 'bool':
return Boolean(value);
case 'string':
return String(value);
case 'bytes':
case 'bytes32':
return value instanceof Uint8Array ? (0, protobuf_1.bytesToHex)(value) : value;
default:
if (solidityType.startsWith('uint') || solidityType.startsWith('int')) {
return BigInt(value);
}
return value;
}
}
/**
* Convert Solidity type to JavaScript value
*/
static fromSolidityType(value, solidityType) {
switch (solidityType) {
case 'address':
return value.toLowerCase();
case 'bool':
return Boolean(value);
case 'string':
return String(value);
case 'bytes':
case 'bytes32':
return typeof value === 'string' ? (0, protobuf_1.hexToBytes)(value) : value;
default:
if (solidityType.startsWith('uint') || solidityType.startsWith('int')) {
return typeof value === 'bigint' ? value : BigInt(value);
}
return value;
}
}
/**
* Validate value against Solidity type
*/
static validateType(value, solidityType) {
try {
this.toSolidityType(value, solidityType);
return true;
}
catch {
return false;
}
}
}
exports.SolidityTypeUtils = SolidityTypeUtils;
/**
* Auto-detect payload format and decode accordingly
*/
class PayloadAutoDecoder {
/**
* Attempt to decode payload using multiple strategies
*/
static decode(payload) {
// Try ABI decoding first
if (AbiCodec.isAbiEncoded(payload)) {
return {
format: 'abi',
data: payload,
confidence: 0.8,
};
}
// Try JSON decoding
try {
const text = new TextDecoder().decode(payload);
const json = JSON.parse(text);
return {
format: 'json',
data: json,
confidence: 0.9,
};
}
catch {
// Not JSON
}
// Try string decoding
try {
const text = new TextDecoder().decode(payload);
// Check if it's valid UTF-8 and printable
if (text.length > 0 && !/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/.test(text)) {
return {
format: 'string',
data: text,
confidence: 0.7,
};
}
}
catch {
// Not valid UTF-8
}
// Default to raw bytes
return {
format: 'raw',
data: payload,
confidence: 0.5,
};
}
}
exports.PayloadAutoDecoder = PayloadAutoDecoder;
//# sourceMappingURL=abiUtils.js.map