A Java SDK for interacting with the Unicity network, enabling state transitions and token operations with cross-platform support for JVM and Android 12+.
- Token Operations: Mint, transfer, and manage fungible tokens
- State Transitions: Submit and verify state transitions on the Unicity network
- Cross-Platform: Works on standard JVM and Android 12+ (API level 31+)
- Type-Safe: Strongly typed API with comprehensive error handling
- CBOR Support: Built-in CBOR encoding/decoding using Jackson
- Async Operations: All network operations return
CompletableFuture
for non-blocking execution
- Java 11 or higher
- Android 12+ (API level 31+) for Android platform
- Gradle 8.8 or higher
Add JitPack repository:
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.unicitynetwork:java-state-transition-sdk:1.1:android'
}
dependencies {
implementation 'com.github.unicitynetwork:java-state-transition-sdk:1.1:jvm'
}
dependencies {
implementation 'org.unicitylabs:java-state-transition-sdk:1.1-SNAPSHOT'
}
private static final SecureRandom RANDOM = new SecureRandom();
/**
* Generate random bytes of specified length.
*/
public static byte[] randomBytes(int length) {
byte[] bytes = new byte[length];
RANDOM.nextBytes(bytes);
return bytes;
}
// Connect to the Unicity test network
String aggregatorUrl = "https://gateway-test.unicity.network";
DefaultAggregatorClient aggregatorClient = new DefaultAggregatorClient(aggregatorUrl);
StateTransitionClient client = new StateTransitionClient(aggregatorClient);
// Create root trust base from classpath
RootTrustBase trustbase = RootTrustBase.fromJson(
new String(getClass().getResourceAsStream("/trust-base.json").readAllBytes())
);
byte[] secret = "minter_secret".getBytes(StandardCharsets.UTF_8);
// Generate data for token
TokenId tokenId = new TokenId(randomBytes(32));
TokenType tokenType = new TokenType(randomBytes(32));
byte[] tokenData = "token immutable data".getBytes(StandardCharsets.UTF_8);
TokenCoinData coinData = new TokenCoinData(
Map.of(
new CoinId("coin".getBytes()), BigInteger.valueOf(100),
new CoinId("second coin".getBytes()), BigInteger.valueOf(5)
)
);
// Create predicate for initial state and use its reference as address
byte[] nonce = randomBytes(32);
// Create key pair from nonce and secret
SigningService signingService = SigningService.createFromMaskedSecret(secret, nonce);
MaskedPredicate predicate = MaskedPredicate.create(
tokenId,
tokenType,
signingService,
HashAlgorithm.SHA256,
nonce
);
byte[] salt = randomBytes(32);
MintCommitment<?> commitment = MintCommitment.create(
new MintTransaction.Data<>(
tokenId,
tokenType,
tokenData,
coinData,
predicate.getReference().toAddress(),
salt,
null,
null
)
);
// Submit mint transaction using StateTransitionClient
SubmitCommitmentResponse response = client
.submitCommitment(commitment)
.get();
if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) {
throw new Exception(
String.format(
"Failed to submit mint commitment: %s",
response.getStatus()
)
);
}
// Wait for inclusion proof
InclusionProof inclusionProof = InclusionProofUtils.waitInclusionProof(
client,
trustBase,
commitment
).get();
// Create mint transaction
Token<?> token = Token.create(
trustBase,
// Create initial state with transaction data
new TokenState(predicate, null),
commitment.toTransaction(inclusionProof)
);
Long blockHeight = client.getAggregatorClient().getBlockHeight().get();
System.out.println("Current block height: "+blockHeight);
byte[] senderSecret = secret;
byte[] senderNonce = nonce;
String recipientNametag = "RECIPIENT";
byte[] recipientData = "Custom data".getBytes(StandardCharsets.UTF_8);
DataHash recipientDataHash = new DataHasher(HashAlgorithm.SHA256)
.update(recipientData)
.digest();
byte[] salt = randomBytes(32);
// Submit transfer transaction
TransferCommitment transferCommitment = TransferCommitment.create(
token,
ProxyAddress.create(recipientNametag),
salt,
recipientDataHash,
null,
SigningService.createFromMaskedSecret(senderSecret, senderNonce)
);
SubmitCommitmentResponse transferResponse = this.client.submitCommitment(transferCommitment).get();
if (transferResponse.getStatus() != SubmitCommitmentStatus.SUCCESS) {
throw new Exception(
String.format(
"Failed to submit transfer commitment: %s",
transferResponse.getStatus()
)
);
}
// Create transfer transaction
TransferTransaction transferTransaction = transferCommitment.toTransaction(
InclusionProofUtils.waitInclusionProof(
client,
trustBase,
transferCommitment
).get()
);
// Prepare info for sending to recipient
String transferTransactionJson = transferTransaction.toJson();
String tokenJson = token.toJson();
String recipientNametag = "RECIPIENT";
byte[] receiverSecret = "RECEIVER_SECRET".getBytes();
Token<?> token = Token.fromJson("TOKEN JSON");
TransferTransaction transaction = TransferTransaction.fromJson("TRANSFER TRANSACTION JSON");
// Create nametag token
TokenType nametagType = new TokenType(randomBytes(32));
byte[] nametagNonce = randomBytes(32);
byte[] nametagSalt = randomBytes(32);
MintCommitment<?> nametagMintCommitment = MintCommitment.create(
new MintTransaction.NametagData(
recipientNametag,
nametagType,
MaskedPredicateReference.create(
nametagType,
SigningService.createFromMaskedSecret(receiverSecret, nametagNonce),
HashAlgorithm.SHA256,
nametagNonce
).toAddress(),
nametagSalt,
UnmaskedPredicateReference.create(
token.getType(),
SigningService.createFromSecret(receiverSecret),
HashAlgorithm.SHA256
).toAddress()
)
);
// Submit nametag mint transaction using StateTransitionClient
SubmitCommitmentResponse nametagMintResponse = client.submitCommitment(nametagMintCommitment).get();
if (nametagMintResponse.getStatus() != SubmitCommitmentStatus.SUCCESS) {
throw new Exception(
String.format(
"Failed to submit nametag mint commitment: %s",
nametagMintResponse.getStatus()
)
);
}
// Wait for inclusion proof
InclusionProof inclusionProof = InclusionProofUtils.waitInclusionProof(
client,
trustBase,
nametagMintCommitment
).get();
Token<?> nametagToken = Token.create(
trustBase,
new TokenState(
MaskedPredicate.create(
nametagMintCommitment.getTransactionData().getTokenId(),
nametagMintCommitment.getTransactionData().getTokenType(),
SigningService.createFromMaskedSecret(receiverSecret, nametagNonce),
HashAlgorithm.SHA256,
nametagNonce
),
null
),
nametagMintCommitment.toTransaction(inclusionProof)
);
// Receiver finalizes the token
Token<?> finalizedToken = client.finalizeTransaction(
trustBase,
token,
new TokenState(
UnmaskedPredicate.create(
token.getId(),
token.getType(),
SigningService.createFromSecret(receiverSecret),
HashAlgorithm.SHA256,
transaction.getData().getSalt()
),
null
),
transaction,
List.of(nametagToken)
);
git clone https://github.com/unicitynetwork/java-state-transition-sdk.git
cd java-state-transition-sdk
./gradlew build
# Run unit tests
./gradlew test
# Run integration tests (requires Docker)
./gradlew integrationTest
# Run E2E tests against deployed aggregator
AGGREGATOR_URL=https://gateway-test.unicity.network ./gradlew integrationTest
The SDK is compatible with Android 12+ (API level 31+). It uses:
- OkHttp for HTTP operations instead of Java 11's HttpClient
- Android-compatible Guava version
- Animal Sniffer plugin to ensure API compatibility
The standard JVM version uses:
- Java 11 APIs
- Full Guava JRE version
- All Java 11 features
The SDK follows a modular architecture under org.unicitylabs.sdk
:
- api: Core API interfaces and aggregator client
- address: Address schemes and implementations (DirectAddress, ProxyAddress)
- bft: BFT layer data, trustbase, certificates, seals
- hash: Cryptographic hashing (SHA256, SHA224, SHA384, SHA512, RIPEMD160)
- jsonrpc: JSON-RPC transport layer
mtree
: Merkle tree implementationsplain
: Sparse Merkle Tree (SMT)sum
: Sparse Merkle Sum Tree (SMST)
- predicate: Ownership predicates
- serializer: CBOR/JSON serializer utilities
- signing: Digital signature support (ECDSA secp256k1)
- token: Token types
fungible
: Fungible token support with CoinId and TokenCoinDatatransaction
: Transaction types and builderssplit
: Token splitting functionality with TokenSplitBuilder
- util: Utilities
- verification: Verification rules
All certificate and transaction verification is handled internally by the SDK, requiring only the trustbase as input from the user.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
- Follow Java naming conventions
- Add unit tests for new features
- Update documentation as needed
- Ensure compatibility with both JVM and Android platforms
- Run
./gradlew build
before submitting PR
The SDK includes comprehensive test suites:
Located in src/test/java
, these test individual components in isolation.
Located in src/test/java/org/unicitylabs/sdk/
:
integration/TokenIntegrationTest
: Tests against Docker-based local aggregatore2e/TokenE2ETest
: E2E tests using CommonTestFlow (requiresAGGREGATOR_URL
env var)e2e/BasicE2ETest
: Basic connectivity and performance tests
# All tests
./gradlew test
# Integration tests only
./gradlew integrationTest
# Specific test class
./gradlew test --tests "org.unicitylabs.sdk.api.RequestIdTest"
This project is licensed under the MIT License - see the LICENSE file for details.
For issues and feature requests, please use the GitHub issue tracker.
For questions about the Unicity Labs, visit unicity-labs.com.
- Built on the Unicity network protocol
- Uses Jackson for CBOR encoding
- Uses Bouncy Castle for cryptographic operations
- Uses OkHttp for Android-compatible HTTP operations