A comprehensive Java SDK for interacting with the Aptos blockchain, featuring advanced cryptography, multi-signature support, and hierarchical deterministic wallet capabilities.
- π Advanced Cryptography: Ed25519 and MultiEd25519 signature schemes
- π± Hierarchical Deterministic Wallets: BIP39/BIP44 support with mnemonic phrases
- π₯ Multi-Signature Accounts: Threshold-based multi-signature transactions
- π¦ BCS Serialization: Binary Canonical Serialization for Aptos transactions
- π HTTP Client: Robust REST client for Aptos API interactions
- π§ͺ Comprehensive Testing: Extensive test suite covering all functionality
The Japtos SDK is published to Maven Central. No additional repository configuration is needed if you're using Maven Central (which is the default).
If you need to explicitly configure the repository, add this to your pom.xml:
<repositories>
<repository>
<id>central</id>
<name>Maven Central</name>
<url>https://repo1.maven.org/maven2</url>
</repository>
</repositories>Add the Japtos SDK dependency to your pom.xml:
<dependency>
<groupId>io.github.aptos-labs</groupId>
<artifactId>japtos</artifactId>
<version>1.1.8</version>
</dependency>If you prefer to build from source, add the following dependencies to your pom.xml:
<dependencies>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- Cryptography -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.77</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.77</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk18on</artifactId>
<version>1.77</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>import com.aptoslabs.japtos.client.AptosClient;
import com.aptoslabs.japtos.api.AptosConfig;
// Connect to different networks
AptosConfig config = AptosConfig.builder()
.network(AptosConfig.Network.MAINNET) // or TESTNET, DEVNET, LOCALNET
.build();
AptosClient client = new AptosClient(config);The Japtos SDK includes a built-in logging system that provides detailed information about SDK operations. You can configure the logging level when initializing the client:
import com.aptoslabs.japtos.client.AptosClient;
import com.aptoslabs.japtos.api.AptosConfig;
import com.aptoslabs.japtos.utils.LogLevel;
// Configure with specific log level
AptosConfig config = AptosConfig.builder()
.network(AptosConfig.Network.DEVNET)
.logLevel(LogLevel.INFO) // Available: DEBUG, INFO, WARN, ERROR
.build();
AptosClient client = new AptosClient(config);
// Output: [2025-11-13 11:09:32.851] [INFO] Japtos SDK v1.1.6 initialized [DEVNET] - Support: https://github.com/aptos-labs/japtosLog Levels:
LogLevel.DEBUG- Detailed information for debugging (default)LogLevel.INFO- General information messagesLogLevel.WARN- Warning messagesLogLevel.ERROR- Only error messages
Example with different log levels:
// Production environment - only show warnings and errors
AptosConfig prodConfig = AptosConfig.builder()
.network(AptosConfig.Network.MAINNET)
.logLevel(LogLevel.WARN)
.build();
// Development environment - show all logs
AptosConfig devConfig = AptosConfig.builder()
.network(AptosConfig.Network.DEVNET)
.logLevel(LogLevel.DEBUG)
.build();
// Custom network with error-only logging
AptosConfig customConfig = AptosConfig.builder()
.fullnode("https://custom.fullnode.example.com")
.logLevel(LogLevel.ERROR)
.build();Changing log level at runtime:
import com.aptoslabs.japtos.utils.Logger;
// Change log level after initialization
Logger.setLogLevel(LogLevel.DEBUG);
// Get current log level
LogLevel currentLevel = Logger.getLogLevel();Generate a new account:
import com.aptoslabs.japtos.account.Ed25519Account;
// Generate a new Ed25519 account
Ed25519Account account = Ed25519Account.generate();
System.out.
println("Address: "+account.getAccountAddress());
System.out.
println("Public Key: "+account.getPublicKey());
System.out.
println("Private Key: "+account.getPrivateKey());Create account from private key:
import com.aptoslabs.japtos.core.crypto.Ed25519PrivateKey;
// Create account from existing private key
Ed25519PrivateKey privateKey = Ed25519PrivateKey.fromHex("your_private_key_hex");
Ed25519Account account = new Ed25519Account(privateKey, null);Derive account from mnemonic phrase:
import com.aptoslabs.japtos.account.Account;
// Derive account using BIP44 path
String mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
String derivationPath = "m/44'/637'/0'/0'/0'"; // Aptos coin type: 637
Ed25519Account account = Account.fromDerivationPath(derivationPath, mnemonic);Convert entropy to mnemonic:
import com.aptoslabs.japtos.utils.Bip39Utils;
// Convert UUID/entropy to mnemonic phrase
String entropy = "9b4c9e83-a06e-4704-bc5f-b6a55d0dbb89";
String mnemonic = Bip39Utils.entropyToMnemonic(entropy);
// Result: "defense balance boat index fatal book remain champion cushion city escape huge"Create 1-of-2 multi-signature account:
import com.aptoslabs.japtos.account.MultiEd25519Account;
import com.aptoslabs.japtos.core.crypto.Ed25519PrivateKey;
// Create two accounts
Ed25519Account account1 = Ed25519Account.generate();
Ed25519Account account2 = Ed25519Account.generate();
// Create multi-signature account (1-of-2 threshold)
List<Ed25519PrivateKey> privateKeys = Arrays.asList(
account1.getPrivateKey(),
account2.getPrivateKey()
);
MultiEd25519Account multiAccount = MultiEd25519Account.fromPrivateKeys(privateKeys, 1);Create multi-signature with specific public keys:
// Create multi-signature with specific public keys and signers
List<Account> signers = Arrays.asList(account1);
List<Ed25519PublicKey> publicKeys = Arrays.asList(
account1.getPublicKey(),
account2.getPublicKey(),
account3.getPublicKey()
);
MultiEd25519Account multiAccount = MultiEd25519Account.from(signers, publicKeys, 1);MultiKey with mixed key types (Ed25519 + Keyless):
import com.aptoslabs.japtos.account.MultiKeyAccount;
import com.aptoslabs.japtos.core.crypto.KeylessPublicKey;
import com.aptoslabs.japtos.core.crypto.PublicKey;
// Keyless public key from OAuth/passkey authentication
String keylessHex = "1b68747470733a2f2f6163636f756e74732e676f6f676c652e636f6d20...";
KeylessPublicKey keylessKey = KeylessPublicKey.fromHexString(keylessHex);
// Traditional Ed25519 account
Ed25519Account passWallet = Ed25519Account.generate();
// Create MultiKey with mixed types (threshold 1-of-2)
List<PublicKey> publicKeys = Arrays.asList(keylessKey, passWallet.getPublicKey());
List<Account> signers = Arrays.asList(passWallet);
MultiKeyAccount multiKey = MultiKeyAccount.fromPublicKeysAndSigners(publicKeys, signers, 1);
System.out.println("MultiKey address: " + multiKey.getAccountAddress());Simple APT transfer:
import com.aptoslabs.japtos.transaction.RawTransaction;
import com.aptoslabs.japtos.transaction.SignedTransaction;
import com.aptoslabs.japtos.types.*;
// Build transfer payload
ModuleId moduleId = new ModuleId(
AccountAddress.fromHex("0x0000000000000000000000000000000000000000000000000000000000000001"),
new Identifier("coin")
);
TransactionPayload payload = new EntryFunctionPayload(
moduleId,
new Identifier("transfer"),
Arrays.asList(new TypeTag.Struct(new StructTag(
AccountAddress.fromHex("0x0000000000000000000000000000000000000000000000000000000000000001"),
new Identifier("aptos_coin"),
new Identifier("AptosCoin"),
Arrays.asList()
))),
Arrays.asList(
new TransactionArgument.AccountAddress(recipientAddress),
new TransactionArgument.U64(1000000L) // 1 APT
)
);
// Build and sign transaction
RawTransaction rawTx = new RawTransaction(
account.getAccountAddress(),
sequenceNumber,
payload,
1000000L, // maxGasAmount
100L, // gasUnitPrice
System.currentTimeMillis() / 1000 + 3600, // expiration
chainId
);
SignedTransaction signedTx = new SignedTransaction(rawTx, account.signTransactionWithAuthenticator(rawTx));Submit and wait for transaction:
// Submit transaction
PendingTransaction pendingTx = client.submitTransaction(signedTx);
System.out.println("Transaction Hash: " + pendingTx.getHash());
// Wait for transaction to be committed
Transaction tx = client.waitForTransaction(pendingTx.getHash());
System.out.println("Success: " + tx.isSuccess());Sign and verify messages:
// Sign a message
String message = "Hello, Aptos!";
byte[] messageBytes = message.getBytes();
Signature signature = account.sign(messageBytes);
// Verify signature
boolean isValid = account.verifySignature(messageBytes, signature);
System.out.println("Signature Valid: " + isValid);The SDK includes comprehensive tests demonstrating all functionality:
- Account generation and key management
- Private/public key operations
- Address derivation
- 1-of-2 and 1-of-3 multi-signature setups
- Complex multi-signature configurations
- Transaction signing with multiple signers
- BIP39 mnemonic generation from entropy
- BIP44 path derivation
- UUID to mnemonic conversion
- APT transfers with single and multi-signature accounts
- Balance checking and validation
- Transaction serialization and submission
- End-to-end workflows from account creation to transaction execution
- Funding and balance management
- Real blockchain interactions
Funding is only available for:
- Devnet:
https://fullnode.devnet.aptoslabs.com - Localnet:
http://127.0.0.1:8080
For mainnet and testnet, you'll need to fund accounts through other means (exchanges, faucets, etc.).
// Available networks
AptosConfig.Network.MAINNET // Production network
AptosConfig.Network.TESTNET // Test network
AptosConfig.Network.DEVNET // Development network (funding available)
AptosConfig.Network.LOCALNET // Local network (funding available)# Run all tests
mvn test
# Run specific test class
mvn test -Dtest=MultiKeyTests
# Run specific test method
mvn test -Dtest=MultiKeyTests#testMultikeyPathDerivationEd25519Account: Single-key Ed25519 accountMultiEd25519Account: Multi-signature accountAccount: Abstract base class
Bip39Utils: BIP39 mnemonic operationsBip44Utils: BIP44 derivation utilitiesHexUtils: Hexadecimal encoding/decoding
RawTransaction: Unsigned transactionSignedTransaction: Signed transactionTransactionPayload: Transaction payload types
AptosClient: Main client for API interactionsHttpClient: HTTP client interface
Example Move module with optional parameters:
module example::optional_params {
use std::option::{Self, Option};
use std::string::String;
public entry fun test_options(
account: &signer,
u64_opt: Option<u64>,
string_opt: Option<String>,
bool_opt: Option<bool>,
address_opt: Option<address>
) {
// Function accepts optional parameters
}
public entry fun test_mixed(
account: &signer,
required_u64: u64,
optional_string: Option<String>,
required_bool: bool,
optional_u64: Option<u64>
) {
// Mix of required and optional parameters
}
}Calling Move functions with optional parameters from Java:
import com.aptoslabs.japtos.types.MoveOption;
import com.aptoslabs.japtos.types.TransactionArgument;
// Example 1: All optional parameters with Some values
List<TransactionArgument> args = Arrays.asList(
MoveOption.u64(12345L), // Some(12345)
MoveOption.string("hello from japtos"), // Some("hello from japtos")
MoveOption.bool(true), // Some(true)
MoveOption.address(accountAddress) // Some(0x123...)
);
// Example 2: Mix of Some and None values
List<TransactionArgument> args = Arrays.asList(
MoveOption.u64(null), // None
MoveOption.string("optional value"), // Some("optional value")
MoveOption.bool(null), // None
MoveOption.address(null) // None
);
// Example 3: Mixed required and optional parameters
AccountAddress recipient = AccountAddress.fromHex("0x1");
List<TransactionArgument> args = Arrays.asList(
new TransactionArgument.U64(456L), // required u64
MoveOption.string("optional value"), // optional string: Some("optional value")
new TransactionArgument.Bool(false), // required bool
MoveOption.u64(null) // optional u64: None
);
// Build and submit transaction
ModuleId moduleId = new ModuleId(moduleAddress, new Identifier("optional_params"));
EntryFunctionPayload payload = new EntryFunctionPayload(
moduleId,
new Identifier("test_mixed"),
Collections.emptyList(),
args
);
// MoveOption factory methods for all types
MoveOption.u8((byte) 1); // Some(1)
MoveOption.u16((short) 100); // Some(100)
MoveOption.u32(1000); // Some(1000)
MoveOption.u64(10000L); // Some(10000)
MoveOption.u128(BigInteger.valueOf(12345)); // Some(12345)
MoveOption.u256(new BigInteger("999999")); // Some(999999)
MoveOption.bool(true); // Some(true)
MoveOption.string("hello"); // Some("hello")
MoveOption.address(accountAddr); // Some(address)
MoveOption.u8Vector(new byte[]{1, 2, 3}); // Some([1,2,3])
// Working with MoveOption values
MoveOption<TransactionArgument.U64> amount = MoveOption.u64(1000L);
if (amount.isSome()) {
TransactionArgument.U64 value = amount.unwrap();
System.out.println("Amount: " + value.getValue());
}
// Convert to Java Optional
Optional<TransactionArgument.U64> javaOpt = amount.toOptional();The SDK supports gas-sponsored transactions where a third party pays for transaction fees. This is useful for onboarding users without requiring them to hold APT for gas.
Setup:
import com.aptoslabs.japtos.gasstation.*;
// Option 1: Using GasStationSettings with AptosConfig
GasStationSettings settings = GasStationSettings.builder()
.apiKey("your_api_key_here")
.endpoint("https://gas-station.testnet.aptoslabs.com")
.build();
AptosConfig config = AptosConfig.builder()
.network(AptosConfig.Network.TESTNET)
.plugin(settings)
.build();
// Option 2: Direct client creation
GasStationClientOptions options = new GasStationClientOptions.Builder()
.network(AptosConfig.Network.TESTNET)
.apiKey("your_api_key_here")
.build();
AccountAddress feePayerAddress = AccountAddress.fromHex("0x...");
GasStationTransactionSubmitter gasStation = new GasStationTransactionSubmitter(options, feePayerAddress);
AptosConfig config = AptosConfig.builder()
.network(AptosConfig.Network.TESTNET)
.transactionSubmitter(gasStation)
.build();
AptosClient client = new AptosClient(config);Signing for fee payer transactions:
When using gas station, you must sign with the fee payer context:
// Build your transaction
RawTransaction rawTx = new RawTransaction(...);
// Create FeePayerRawTransaction for signing
FeePayerRawTransaction feePayerTxn = new FeePayerRawTransaction(
rawTx,
List.of(), // secondary signers
feePayerAddress
);
// Sign with fee payer salt
byte[] feePayerBytes = feePayerTxn.bcsToBytes();
byte[] domain = "APTOS::RawTransactionWithData".getBytes();
byte[] prefixHash = CryptoUtils.sha3_256(domain);
byte[] signingMessage = new byte[prefixHash.length + feePayerBytes.length];
System.arraycopy(prefixHash, 0, signingMessage, 0, prefixHash.length);
System.arraycopy(feePayerBytes, 0, signingMessage, prefixHash.length, feePayerBytes.length);
// Sign and create transaction
Signature signature = account.sign(signingMessage);
AccountAuthenticator auth = new Ed25519Authenticator(account.getPublicKey(), signature);
SignedTransaction signedTx = new SignedTransaction(rawTx, auth);
// Submit - gas will be paid by the sponsor
PendingTransaction pending = client.submitTransaction(signedTx);The transaction will be submitted with the fee payer covering gas costs. Your account's APT balance remains unchanged.
Transaction submission fails with "sequence number too old"
- Ensure you're using the latest sequence number from the blockchain
- Check that no other transactions are pending for the same account
- Use
client.getNextSequenceNumber(address)to get the current sequence number
"Account not found" errors
- New accounts need to be funded before they can send transactions
- Use the faucet on devnet/testnet to fund accounts
- Check that the account address is correctly formatted (32 bytes, hex string)
BCS serialization errors
- Ensure all transaction arguments match the expected Move types
- Check that optional parameters use
MoveOptionwrapper classes - Verify that addresses are properly formatted AccountAddress objects
Gas station transaction failures
- Verify your API key is valid and has sufficient balance
- Check that the fee payer address matches your gas station configuration
- Ensure you're signing with the correct domain prefix for fee payer transactions
Network connection issues
- Verify the fullnode URL is correct and accessible
- Check network connectivity and firewall settings
- For custom networks, ensure the endpoint supports the Aptos REST API
Android compatibility issues
- The SDK uses BouncyCastle which is Android-compatible
- Ensure you're using the correct JDK version (Java 8+)
- Check that ProGuard rules preserve necessary classes if using code obfuscation
- GitHub Issues: Report bugs or request features
- Documentation: Check the Aptos Documentation
- Community: Join the Aptos Discord
- Fork the repository
- Create a feature branch
- Make your changes
- Add comprehensive tests
- Submit a pull request
This project is licensed under the Apache License 2.0.