Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: PR Build

on:
push:
branches: [ 'main', 'master', 'release_**' ]
pull_request:
branches: [ 'main', 'master', 'develop', 'release_**' ]
types: [ opened, synchronize, reopened ]
paths-ignore: [ '**/*.md', '.gitignore', '**/.gitignore', '.editorconfig',
'.gitattributes', 'docs/**', '.github/ISSUE_TEMPLATE/**',
'.github/PULL_REQUEST_TEMPLATE/**', '.github/CODEOWNERS' ]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
checkstyle:
name: Checkstyle
runs-on: ubuntu-22.04
timeout-minutes: 30

steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Set up JDK 8
uses: actions/setup-java@v5
with:
java-version: '8'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ubuntu-22.04-x86_64-jdk8-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }}
restore-keys: ubuntu-22.04-x86_64-jdk8-gradle-

- name: Run checkstyle
run: ./gradlew checkstyleMain --continue --no-daemon

- name: Upload checkstyle reports
if: failure()
uses: actions/upload-artifact@v6
with:
name: checkstyle-reports
path: '*/build/reports/checkstyle/'

unit-test:
name: Unit Tests (JDK ${{ matrix.java }} / ${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- java: '8'
runner: ubuntu-22.04
arch: x86_64
- java: '17'
runner: ubuntu-24.04-arm
arch: aarch64

steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v5
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'

- name: Check Java version
run: java -version

- name: Cache Gradle packages
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ matrix.runner }}-${{ matrix.arch }}-jdk${{ matrix.java }}-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }}
restore-keys: ${{ matrix.runner }}-${{ matrix.arch }}-jdk${{ matrix.java }}-gradle-

- name: Run unit tests
# --continue: keep running remaining modules after a failure so the log
# shows every failing case in one run (failures still fail this step).
run: ./gradlew clean test --continue --no-daemon

- name: Upload test reports
if: failure()
uses: actions/upload-artifact@v6
with:
name: test-reports-jdk${{ matrix.java }}-${{ matrix.arch }}
path: |
*/build/reports/tests/test/
*/build/test-results/test/
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The latest version (built with JDK 1.8) can be found on [Maven Central](https://
### Gradle

```groovy
implementation("io.github.tronprotocol:trident:0.11.0")
implementation("io.github.tronprotocol:trident:1.0.0")
```

### Maven
Expand All @@ -32,7 +32,7 @@ Add repo setting:
<dependency>
<groupId>io.github.tronprotocol</groupId>
<artifactId>trident</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</dependency>
```

Expand All @@ -45,21 +45,24 @@ You can use locally built packages by the following steps:
2. Add the following to your project's `build.gradle`:
```groovy
dependencies {
implementation files('libs/trident-0.11.0.jar')
implementation files('libs/trident-1.0.0.jar')
implementation "com.google.guava:guava:33.0.0-jre"
implementation "io.grpc:grpc-netty-shaded:1.75.0"
implementation "io.grpc:grpc-netty:1.75.0"
implementation "io.grpc:grpc-okhttp:1.75.0"
implementation "io.grpc:grpc-protobuf:1.75.0"
implementation "io.grpc:grpc-stub:1.75.0"
implementation "io.grpc:grpc-netty-shaded:1.81.0"
implementation "io.grpc:grpc-netty:1.81.0"
implementation "io.grpc:grpc-okhttp:1.81.0"
implementation "io.grpc:grpc-protobuf:1.81.0"
implementation "io.grpc:grpc-stub:1.81.0"
implementation "com.google.protobuf:protobuf-java-util:3.25.8"
implementation "org.bouncycastle:bcprov-jdk18on:1.78.1"
implementation "io.vertx:vertx-core:4.5.21"
implementation "io.netty:netty-all:4.1.125.Final"
implementation "org.bouncycastle:bcprov-jdk18on:1.84"
implementation "io.vertx:vertx-core:4.5.27"
implementation platform("io.netty:netty-bom:4.1.135.Final")
implementation "io.netty:netty-buffer"
implementation "com.alibaba.fastjson2:fastjson2:2.0.55"
}
```

> **Note:** If your code directly uses other netty modules (e.g. `io.netty.channel.*`, `io.netty.handler.*`, `io.netty.codec.*`), add the corresponding artifact after the BOM without specifying a version — the version will be resolved by `netty-bom`. For example: `implementation "io.netty:netty-handler"`.

## Quick Start

**Initialize client**
Expand All @@ -73,8 +76,15 @@ ApiWrapper client = ApiWrapper.ofShasta("private key");
// Or nile testnet
ApiWrapper client = ApiWrapper.ofNile("private_key");

//Initialize with special grpc endpoint
ApiWrapper client = new ApiWrapper("grpc endpoint", "solidity grpc endpoint", "private_key");
// Or initialize with a custom grpc endpoint.
ApiWrapper client = new ApiWrapperBuilder("grpc endpoint")
.withGrpcEndpointSolidity("solidity grpc endpoint")
.withPrivateKey("private_key")
.withApiKey("api_key") // Optional: API key from TronGrid
.withTLS() // Optional: or withTLS(new File("xxx.crt"))
.withTimeout(5000) // Optional: request timeout in milliseconds
.addInterceptors(interceptors) // Optional: custom gRPC interceptors
.build();

// Send TRX
TransactionExtention transactionExtention = client.transfer("fromAddress", "toAddress", 100_000_000L); //100TRX
Expand Down Expand Up @@ -120,4 +130,4 @@ Starting from version 0.9.2, releases are published to Maven repository and sign
```
pub: 3149 FCA5 6377 2D11 2624 9C36 CC3F 8CEA 7B0C 74D6
uid: buildtrident@tron.network
```
```
1 change: 1 addition & 0 deletions abi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ description 'TRON Application Binary Interface (ABI) for working with smart cont

dependencies {
implementation project(':utils')
testImplementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
}
43 changes: 43 additions & 0 deletions abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.tron.trident.abi;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
import org.tron.trident.abi.datatypes.CustomError;
import org.tron.trident.abi.datatypes.Type;
import org.tron.trident.crypto.Hash;
import org.tron.trident.utils.Numeric;

/**
* Ethereum custom error encoding. Further limited details are available <a
* href="https://docs.soliditylang.org/en/develop/abi-spec.html#errors">here</a>.
*/
public class CustomErrorEncoder {

private CustomErrorEncoder() {
}

public static String encode(CustomError error) {
return calculateSignatureHash(
buildErrorSignature(error.getName(), error.getParameters()));
}

static <T extends Type> String buildErrorSignature(
String errorName, List<TypeReference<T>> parameters) {

StringBuilder result = new StringBuilder();
result.append(errorName);
result.append("(");
String params =
parameters.stream().map(Utils::getTypeName).collect(Collectors.joining(","));
result.append(params);
result.append(")");
return result.toString();
}

public static String calculateSignatureHash(String errorSignature) {
byte[] input = errorSignature.getBytes(StandardCharsets.UTF_8);
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash).substring(2);
}
}
47 changes: 45 additions & 2 deletions abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.List;
import org.tron.trident.abi.datatypes.Function;
import org.tron.trident.abi.datatypes.StaticArray;
import org.tron.trident.abi.datatypes.StaticStruct;
import org.tron.trident.abi.datatypes.Type;
import org.tron.trident.abi.datatypes.Uint;

Expand Down Expand Up @@ -64,11 +65,53 @@ private static String encodeParameters(
return result.toString();
}

/**
* Encodes a function call including its method signature (selector) and its parameters.
*
* @param methodId the 4-byte selector (8 hex chars)
* @param parameters the list of parameters to encode
* @return the complete ABI-encoded hex string representing the function call
*/
public String encodeWithSelector(String methodId, List<Type> parameters) {
final StringBuilder result = new StringBuilder(methodId);

return encodeParameters(parameters, result);
}

/**
* Encodes parameters using tight packing (abi.encodePacked).
* This is a non-standard ABI encoding used primarily for computing hashes,
* where padding is omitted and dynamic types are concatenated without length prefixes.
*
* @param parameters the list of parameters to pack
* @return the packed ABI-encoded hex string
*/
@Override
protected String encodePackedParameters(List<Type> parameters) {
final StringBuilder result = new StringBuilder();
for (Type parameter : parameters) {
result.append(TypeEncoder.encodePacked(parameter));
}
return result.toString();
}

/**
* Calculates the length of the tuple head (in 32-byte slots) required for the given parameters.
* Crucially, all dynamic types (including StaticArrays containing dynamic elements) always occupy exactly
* 1 slot in the head (for the offset pointer). Pure static arrays and structs are flattened to calculate
* their total inline slot requirement.
*
* @param parameters the list of types to be encoded.
* @return the total number of 32-byte slots required for the tuple head.
*/
@SuppressWarnings("unchecked")
private static int getLength(final List<Type> parameters) {
int count = 0;
for (final Type type : parameters) {
if (type instanceof StaticArray) {
count += ((StaticArray) type).getValue().size();
if (TypeEncoder.isDynamic(type)) {
count++;
} else if (type instanceof StaticArray || type instanceof StaticStruct) {
count += type.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH;
} else {
count++;
}
Expand Down
Loading