MCPcopy Index your code
hub / github.com/esaulpaugh/headlong

github.com/esaulpaugh/headlong @v13.3.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release v13.3.1 ↗ · + Follow
1,364 symbols 5,970 edges 95 files 119 documented · 9%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Maven Central Apache License, Version 2.0, January 2004 jdk1.8+ Java CI GraalVM Maven Gitter

Contract ABI and Recursive Length Prefix made easy for the JVM.

ABI spec: https://solidity.readthedocs.io/en/latest/abi-spec.html

RLP spec: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp

SHA-256 (headlong-13.3.0.jar): 52735a4111dfdc29be4f64608b7fc46c02d401b34cfa4c5527246a85e91ee007

Usage

ABI package

Encoding Function Calls

Function baz = Function.parse("baz(uint32,bool)"); // canonicalizes and parses any signature
// or
Function f2 = Function.fromJson("{\"type\":\"function\",\"name\":\"foo\",\"inputs\":[{\"name\":\"complex_nums\",\"type\":\"tuple[]\",\"components\":[{\"name\":\"real\",\"type\":\"fixed168x10\"},{\"name\":\"imaginary\",\"type\":\"fixed168x10\"}]}]}");

Pair<Long, Boolean> bazArgs = Tuple.of(69L, true);
Tuple complexNums = Single.of(new Tuple[] { Tuple.of(new BigDecimal("0.0090000000"), new BigDecimal("1.9500000000")) });

// Two equivalent styles:
ByteBuffer bazCall = baz.encodeCall(bazArgs);
ByteBuffer bazCall2 = baz.encodeCallWithArgs(69L, true);

System.out.println("baz call hex:\n" + Strings.encode(bazCall) + "\n"); // hexadecimal encoding (without 0x prefix)

Tuple recoveredArgs = baz.decodeCall(bazCall2); // decode the encoding back to the original args

System.out.println("baz args:\n" + recoveredArgs + "\n"); // toString()
System.out.println("equal:\n" + recoveredArgs.equals(bazArgs) + "\n"); // test for equality

System.out.println("baz call debug:\n" + baz.annotateCall(bazCall.array()) + "\n"); // human-readable, for debugging function calls (expects input to start with 4-byte selector)
System.out.println("baz args debug:\n" + baz.getInputs().annotate(bazArgs) + "\n"); // human-readable, for debugging encodings without a selector
System.out.println("f2 call debug:\n" + f2.annotateCall(complexNums) + "\n");
System.out.println("f2 args debug:\n" + f2.getInputs().annotate(complexNums));

Decoding Return Values

Function foo = Function.parse("foo((fixed[],int8)[1][][5])", "(int,string)");

// decode return type (int256,string)
Tuple decoded = foo.decodeReturn(
    FastHex.decode(
          "000000000000000000000000000000000000000000000000000000000000002A"
        + "0000000000000000000000000000000000000000000000000000000000000040"
        + "000000000000000000000000000000000000000000000000000000000000000e"
        + "59616f62616e6745696768747939000000000000000000000000000000000000"
    )
);

System.out.println(decoded.equals(Tuple.of(BigInteger.valueOf(42L), "YaobangEighty9")));
Function fooTwo = Function.parse("fooTwo()", "(uint8)");
int returned = fooTwo.decodeSingletonReturn(FastHex.decode("00000000000000000000000000000000000000000000000000000000000000FF")); // uint8 corresponds to int
System.out.println(returned);

Using TupleType

TupleType<Tuple> tt = TupleType.parse("(bool,address,int72[][])");
ByteBuffer b0 = tt.encode(Tuple.of(false, Address.wrap("0x52908400098527886E0F7030069857D2E4169EE7"), new BigInteger[0][]));
// Tuple t = tt.decode(b0); // decode the tuple (has the side effect of advancing the ByteBuffer's position)
// or...
Address a = tt.decode(b0, 1); // decode only index 1
System.out.println(a);
Tuple t2 = tt.decode(b0, 0, 2); // decode only indices 0 and 2
System.out.println(t2);

ByteBuffer b1 = tt.<ABIType<BigInteger[][]>>get(2).encode(new BigInteger[][] {  }); // encode only int72[][]

Misc

Event<?> event = Event.fromJson("{\"type\":\"event\",\"name\":\"an_event\",\"inputs\":[{\"name\":\"a\",\"type\":\"bytes\",\"indexed\":true},{\"name\":\"b\",\"type\":\"uint256\",\"indexed\":false}],\"anonymous\":true}");
Tuple args = event.decodeArgs(new byte[][] { new byte[32] }, new byte[32]);
System.out.println(event);
System.out.println(args);

// create any type directly (advanced)
ArrayType<ABIType<Object>, ?, Object> at = TypeFactory.create("(address,int)[]");
ArrayType<TupleType<Tuple>, Tuple, Tuple[]> at2 = TypeFactory.create("(address,int)[]");
ArrayType<TupleType<Pair<Address, BigInteger>>, Pair<Address, BigInteger>, Pair<Address, BigInteger>[]> at3 = TypeFactory.create("(address,int)[]");
ABIType<Object> unknown = TypeFactory.create(at.getCanonicalType());

RLP package

// for an example class Student implementing some example interface
public Student(byte[] rlp) {
    Iterator<RLPItem> iter = RLPDecoder.RLP_STRICT.sequenceIterator(rlp);

    this.name = iter.next().asString(Strings.UTF_8);
    this.gpa = iter.next().asFloat(false);
    this.publicKey = iter.next().asBytes();
    this.balance = new BigDecimal(iter.next().asBigInt(), iter.next().asInt());
}

@Override
public Object[] toObjectArray() {
    return new Object[] {
            // instances of byte[]
            Strings.decode(name, Strings.UTF_8),
            FloatingPoint.toBytes(gpa),
            publicKey,
            balance.unscaledValue().toByteArray(),
            Integers.toBytes(balance.scale())
            // include an Object[] or Iterable and its elements will be encoded as an RLP list (which may include other lists)
    };
}

@Override
public byte[] toRLP() {
    return RLPEncoder.sequence(toObjectArray());
}

Build

Now available in Maven Central Repository.

Or build locally:

Clone the project and install to your local maven repository using gradle publishToMavenLocal or mvn install, then declare it as a dependency:

implementation("com.esaulpaugh:headlong:13.3.1-SNAPSHOT")
<dependency>
    <groupId>com.esaulpaugh</groupId>
    <artifactId>headlong</artifactId>
    <version>13.3.1-SNAPSHOT</version>
</dependency>

Alternatively:

  • Run gradle build or gradle jar which output to build/libs
  • Use mvn package which outputs to target
  • Execute ant all (optionally with "-Dgradle-cache=true") which outputs to build/lib
  • Add headlong as a project dependency

Benchmarks

Screenshot graalvm-jdk-24 VM 24.2.0 (aarch64 JIT) on Apple M3 Max

Command line interface

https://github.com/esaulpaugh/headlong-cli

Example Android app

https://github.com/esaulpaugh/headlong-android

Misc

Also includes optimized implementations of:

  • EIP-778 Ethereum Node Records
  • EIP-55 Mixed-case checksum address encoding
  • Keccak
  • hexadecimal

headlong depends on gson v2.1 or greater at runtime and on v2.12.0 or greater at compile time. Test suite should take less than one minute to run. Test packages require junit. Jar size is ~137 KiB. Java 8+.

For better contract ABI JSON parsing performance, consider constructing an ABIParser with a Set<TypeEnum> by which to filter objects by type. For best performance, json should be compact and "type" should be the first key in functions, events, and errors. This can be done via ABIJSON.optimize(String).

See the wiki for more, such as packed encoding (and decoding) and RLP Object Notation: https://github.com/esaulpaugh/headlong/wiki

Licensed under Apache 2.0 terms

Extension points exported contracts — how you extend this code

ABIObject (Interface)
Supertype of json-encodeable types Function, Event, and ContractError. [3 implementers]
src/main/java/com/esaulpaugh/headlong/abi/ABIObject.java
BiIntPredicate (Interface)
(no doc) [4 implementers]
src/test/java/com/esaulpaugh/headlong/rlp/RLPDecoderTest.java
Signer (Interface)
(no doc) [1 implementers]
src/main/java/com/esaulpaugh/headlong/rlp/Record.java
RLPAdapter (Interface)
(no doc) [4 implementers]
src/test/java/com/esaulpaugh/headlong/rlp/example/RLPAdapter.java
Verifier (Interface)
(no doc)
src/main/java/com/esaulpaugh/headlong/rlp/Record.java
CustomRunnable (Interface)
(no doc) [2 implementers]
src/test/java/com/esaulpaugh/headlong/TestUtils.java
ParallelRun (Interface)
(no doc) [2 implementers]
src/test/java/com/esaulpaugh/headlong/TestUtils.java
RLPEncodeable (Interface)
(no doc) [2 implementers]
src/test/java/com/esaulpaugh/headlong/rlp/example/RLPEncodeable.java

Core symbols most depended-on inside this repo

get
called by 172
src/main/java/com/esaulpaugh/headlong/abi/Tuple.java
parse
called by 98
src/main/java/com/esaulpaugh/headlong/abi/TupleType.java
decode
called by 95
src/test/java/com/esaulpaugh/headlong/rlp/example/RLPAdapter.java
encode
called by 94
src/main/java/com/esaulpaugh/headlong/util/Strings.java
size
called by 91
src/main/java/com/esaulpaugh/headlong/abi/Tuple.java
create
called by 80
src/main/java/com/esaulpaugh/headlong/abi/TypeFactory.java
decode
called by 79
src/main/java/com/esaulpaugh/headlong/util/Strings.java
length
called by 72
src/main/java/com/esaulpaugh/headlong/abi/CharSequenceView.java

Shape

Method 1,247
Class 105
Interface 9
Enum 3

Languages

Java100%

Modules by API surface

src/test/java/com/esaulpaugh/headlong/TestUtils.java58 symbols
src/test/java/com/esaulpaugh/headlong/rlp/RLPDecoderTest.java53 symbols
src/main/java/com/esaulpaugh/headlong/abi/ArrayType.java53 symbols
src/test/java/com/esaulpaugh/headlong/abi/DecodeTest.java44 symbols
src/test/java/com/esaulpaugh/headlong/abi/MonteCarloTestCase.java40 symbols
src/main/java/com/esaulpaugh/headlong/abi/TupleType.java39 symbols
src/main/java/com/esaulpaugh/headlong/abi/ABIType.java39 symbols
src/test/java/com/esaulpaugh/headlong/abi/TupleTest.java38 symbols
src/test/java/com/esaulpaugh/headlong/abi/EncodeTest.java34 symbols
src/test/java/com/esaulpaugh/headlong/abi/ABIJSONTest.java33 symbols
src/main/java/com/esaulpaugh/headlong/rlp/RLPItem.java32 symbols
src/main/java/com/esaulpaugh/headlong/abi/Function.java32 symbols

For agents

$ claude mcp add headlong \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact