Tech Stack

May 25, 2026 · View on GitHub

What the extension is built with, why, and how it's packaged.

At a glance

LayerChoice
LanguageJava 21 (see Runtime)
BuildGradle 8.8 (wrapper), multi-project
Host platformBurp Suite via the Montoya API
Web3 codecweb3j core 5.0.2
JSONJackson Databind (transitive) + org.json
CryptoBouncyCastle (transitive, via web3j)
TestsJUnit 5 (Jupiter) + Mockito
OutputSelf-contained ("fat") JAR loaded by Burp

Dependencies

Declared in web3-decoder/build.gradle:

DependencyVersionScopeWhy it's here
net.portswigger.burp.extensions:montoya-api+ (latest)implementationThe Burp extension API: editor tabs, suite tab, HTTP sending, persistence, logging.
org.web3j:core5.0.2implementationABI encoding/decoding (FunctionEncoder, FunctionReturnDecoder), Keccak hashing (Hash), hex utils (Numeric). The core of the decoder.
org.json:json20240303implementationLightweight JSON parsing for explorer / 4byte HTTP responses in the provider classes.
org.junit.jupiter:junit-jupiter-api5.11.3testTest API.
org.junit.jupiter:junit-jupiter-engine5.9.3testRuntimeJUnit 5 engine.
org.mockito:mockito-core5.12.0testMocking the Montoya API and providers in unit tests.

Transitive (not declared, relied upon)

  • Jackson Databind (com.fasterxml.jackson.*) — the primary JSON mapper used across the codebase (ObjectMapper, JsonNode, TypeReference) for request/ response bodies, ABI parsing, and config serialization. It is pulled in via web3j.
  • BouncyCastle — cryptographic primitives required by web3j.

Because Jackson and BouncyCastle are transitive, a major web3j upgrade can shift their versions. They are load-bearing here, so re-test decoding after any web3j bump.

Why web3j (and the custom layer on top)

web3j provides the canonical Solidity ABI type system and codec. However, the extension decodes against arbitrary, externally-sourced ABIs rather than compile-time generated contract wrappers. ABIInputDecoder therefore drives web3j reflectively:

  • maps ABI JSON type strings (uint256, bytes32, address[], tuple[], …) to web3j Type classes and TypeReferences at runtime;
  • decodes/encodes nested structs/tuples using web3j's native inner-type support (TypeReference(false, innerTypes)), available since web3j 4.12.1 — this replaced an earlier bytecode-generation approach and is why the project is on web3j 5.x;
  • renders decoded structs as ordered, named maps when component names are available.

Runtime / JDK

  • build.gradle leaves sourceCompatibility/targetCompatibility commented out, so compilation targets the toolchain's default. settings.gradle applies the foojay-resolver convention plugin, allowing Gradle to auto-provision a JDK.
  • In practice the stack targets Java 21: web3j 5.x requires it, and Burp Suite ships and runs the extension on its own bundled JRE (21 on current releases). Build with a JDK 21 toolchain.

Build & packaging

flowchart LR
    SRC["Java sources +<br/>resources"] --> COMPILE["gradle build<br/>(compile + test)"]
    COMPILE --> JARTASK["gradle jar<br/>(fat jar)"]
    DEPS["runtimeClasspath<br/>(web3j, jackson, org.json, ...)"] --> JARTASK
    JARTASK --> FATJAR["web3-decoder.jar<br/>(all deps bundled)"]
    FATJAR --> BURP["Load in Burp:<br/>Extensions → Add → Java"]

The jar task assembles a self-contained artifact:

  • bundles every runtime dependency (configurations.runtimeClasspathzipTree);
  • duplicatesStrategy = EXCLUDE to tolerate overlapping META-INF entries;
  • excludes META-INF/*.SF|*.DSA|*.RSA so bundled signed jars don't trip JAR signature verification;
  • sets Main-Class to com.nccgroup.web3decoder.decoder.ABIInputDecoder.

A convenience buildAndJar task chains build then jar.

Common commands

# Build, run tests, and produce the loadable jar
./gradlew :web3-decoder:build :web3-decoder:jar

# Run only the tests
./gradlew :web3-decoder:test

# One-shot build + jar
./gradlew buildAndJar

Then in Burp: Extensions → Add → Extension type: Java → select the jar.

CLI mode (bonus)

Because Main-Class points at ABIInputDecoder, the same jar runs standalone for quick decode/encode without Burp:

# Decode calldata against an ABI file
java -jar web3-decoder.jar path/to/abi.json 0xa9059cbb...

# Encode: ABI file, function signature, args as JSON
java -jar web3-decoder.jar path/to/abi.json "transfer(address,uint256)" '{"to":"0x...","amount":"1000"}'

External services contacted at runtime

ServiceUsed byPurpose
Configured JSON-RPC node (the proxied endpoint)Web3RequestContextDecoder, ProxyDetectoreth_chainId (chain detection) and eth_getStorageAt (proxy slot reads).
api.etherscan.io (Etherscan v2) + legacy explorer hostsEtherscanAbiProviderFetch verified contract ABIs.
api.4byte.sourcify.devFourByteSignatureProviderResolve unknown 4-byte selectors to candidate signatures.

All outbound HTTP is sent through Burp's own HTTP stack (api.http().sendRequest), so it respects Burp's upstream proxy/TLS configuration.