Skip to content

aptos-labs/japtos

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

28 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Japtos - Aptos Java SDK

A comprehensive Java SDK for interacting with the Aptos blockchain, featuring advanced cryptography, multi-signature support, and hierarchical deterministic wallet capabilities.

πŸš€ Features

  • πŸ” 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

πŸ“¦ Maven Integration

Repository Configuration

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>

Dependency

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>

πŸ”§ Manual Installation

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>

πŸ—οΈ Quick Start

Initialize Client

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);

πŸ“Š Logging Configuration

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/japtos

Log Levels:

  • LogLevel.DEBUG - Detailed information for debugging (default)
  • LogLevel.INFO - General information messages
  • LogLevel.WARN - Warning messages
  • LogLevel.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();

πŸ“š Usage Examples

πŸ”‘ Basic Account Management

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);

🌱 Hierarchical Deterministic Wallets

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"

πŸ‘₯ Multi-Signature Accounts

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());

πŸ’° Transaction Management

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());

πŸ” Message Signing

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);

πŸ§ͺ Testing Scenarios

The SDK includes comprehensive tests demonstrating all functionality:

Basic Account Tests

  • Account generation and key management
  • Private/public key operations
  • Address derivation

Multi-Signature Tests

  • 1-of-2 and 1-of-3 multi-signature setups
  • Complex multi-signature configurations
  • Transaction signing with multiple signers

Hierarchical Deterministic Wallet Tests

  • BIP39 mnemonic generation from entropy
  • BIP44 path derivation
  • UUID to mnemonic conversion

Transaction Tests

  • APT transfers with single and multi-signature accounts
  • Balance checking and validation
  • Transaction serialization and submission

Integration Tests

  • End-to-end workflows from account creation to transaction execution
  • Funding and balance management
  • Real blockchain interactions

⚠️ Important Notes

Network Support

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.).

Network Configuration

// 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)

πŸƒβ€β™‚οΈ Running Tests

# Run all tests
mvn test

# Run specific test class
mvn test -Dtest=MultiKeyTests

# Run specific test method
mvn test -Dtest=MultiKeyTests#testMultikeyPathDerivation

πŸ“– API Reference

Account Classes

  • Ed25519Account: Single-key Ed25519 account
  • MultiEd25519Account: Multi-signature account
  • Account: Abstract base class

Utility Classes

  • Bip39Utils: BIP39 mnemonic operations
  • Bip44Utils: BIP44 derivation utilities
  • HexUtils: Hexadecimal encoding/decoding

Transaction Classes

  • RawTransaction: Unsigned transaction
  • SignedTransaction: Signed transaction
  • TransactionPayload: Transaction payload types

Client Classes

  • AptosClient: Main client for API interactions
  • HttpClient: HTTP client interface

πŸ”§ Working with Move Option Types

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();

β›½ Gas Station (Sponsored Transactions)

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.

πŸ”§ Troubleshooting

Common Issues

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 MoveOption wrapper 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

Getting Help

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add comprehensive tests
  5. Submit a pull request

πŸ“„ License

This project is licensed under the Apache License 2.0.

πŸ”— References

About

Aptos Java SDK

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors 4

  •  
  •  
  •  
  •  

Languages