Tech Stack
May 25, 2026 · View on GitHub
What the extension is built with, why, and how it's packaged.
At a glance
| Layer | Choice |
|---|---|
| Language | Java 21 (see Runtime) |
| Build | Gradle 8.8 (wrapper), multi-project |
| Host platform | Burp Suite via the Montoya API |
| Web3 codec | web3j core 5.0.2 |
| JSON | Jackson Databind (transitive) + org.json |
| Crypto | BouncyCastle (transitive, via web3j) |
| Tests | JUnit 5 (Jupiter) + Mockito |
| Output | Self-contained ("fat") JAR loaded by Burp |
Dependencies
Declared in web3-decoder/build.gradle:
| Dependency | Version | Scope | Why it's here |
|---|---|---|---|
net.portswigger.burp.extensions:montoya-api | + (latest) | implementation | The Burp extension API: editor tabs, suite tab, HTTP sending, persistence, logging. |
org.web3j:core | 5.0.2 | implementation | ABI encoding/decoding (FunctionEncoder, FunctionReturnDecoder), Keccak hashing (Hash), hex utils (Numeric). The core of the decoder. |
org.json:json | 20240303 | implementation | Lightweight JSON parsing for explorer / 4byte HTTP responses in the provider classes. |
org.junit.jupiter:junit-jupiter-api | 5.11.3 | test | Test API. |
org.junit.jupiter:junit-jupiter-engine | 5.9.3 | testRuntime | JUnit 5 engine. |
org.mockito:mockito-core | 5.12.0 | test | Mocking 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 web3jTypeclasses andTypeReferences 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.gradleleavessourceCompatibility/targetCompatibilitycommented out, so compilation targets the toolchain's default.settings.gradleapplies 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.runtimeClasspath→zipTree); duplicatesStrategy = EXCLUDEto tolerate overlappingMETA-INFentries;- excludes
META-INF/*.SF|*.DSA|*.RSAso bundled signed jars don't trip JAR signature verification; - sets
Main-Classtocom.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
| Service | Used by | Purpose |
|---|---|---|
| Configured JSON-RPC node (the proxied endpoint) | Web3RequestContextDecoder, ProxyDetector | eth_chainId (chain detection) and eth_getStorageAt (proxy slot reads). |
api.etherscan.io (Etherscan v2) + legacy explorer hosts | EtherscanAbiProvider | Fetch verified contract ABIs. |
api.4byte.sourcify.dev | FourByteSignatureProvider | Resolve 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.