Saltearse al contenido

Building Transactions

Esta página aún no está disponible en tu idioma.

Transactions are the primary way to interact with the Aptos blockchain. They allow you to transfer tokens, call smart contract functions, deploy modules, and more.

A typical transaction flow involves:

  1. Build - Create a RawTransaction with the transaction details
  2. Sign - Sign the transaction with the sender’s private key
  3. Submit - Send the signed transaction to the network
  4. Wait - Wait for the transaction to be executed
  5. Verify - Check the transaction result

Every transaction in Aptos requires:

  • Sender - The account address sending the transaction
  • Sequence Number - The current sequence number of the sender’s account
  • Payload - The action to perform (transfer, function call, etc.)
  • Max Gas Amount - Maximum gas willing to pay
  • Gas Unit Price - Price per unit of gas
  • Expiration Timestamp - When the transaction expires
  • Chain ID - The network chain identifier

The most common transaction type is transferring APT tokens between accounts.

import com.aptoslabs.japtos.account.Ed25519Account;
import com.aptoslabs.japtos.api.AptosConfig;
import com.aptoslabs.japtos.client.AptosClient;
import com.aptoslabs.japtos.transaction.*;
import com.aptoslabs.japtos.types.*;
import java.util.Arrays;
// Setup
AptosConfig config = AptosConfig.builder()
.network(AptosConfig.Network.DEVNET)
.build();
AptosClient client = new AptosClient(config);
// Create accounts
Ed25519Account sender = Ed25519Account.generate();
Ed25519Account receiver = Ed25519Account.generate();
// Fund sender
client.fundAccount(sender.getAccountAddress(), 100_000_000);
// Build transfer payload
ModuleId moduleId = new ModuleId(
AccountAddress.fromHex("0x1"),
new Identifier("aptos_account")
);
TransactionPayload payload = new EntryFunctionPayload(
moduleId,
new Identifier("transfer"),
Arrays.asList(), // type arguments (empty for simple transfer)
Arrays.asList(
new TransactionArgument.AccountAddress(receiver.getAccountAddress()),
new TransactionArgument.U64(1_000_000L) // 0.01 APT
)
);
// Get account info
long sequenceNumber = client.getAccountSequenceNumber(sender.getAccountAddress());
int chainId = client.getChainId();
// Build raw transaction
RawTransaction rawTx = new RawTransaction(
sender.getAccountAddress(),
sequenceNumber,
payload,
200000L, // maxGasAmount
100L, // gasUnitPrice
System.currentTimeMillis() / 1000 + 600, // expires in 10 minutes
chainId
);
// Sign and submit
SignedTransaction signedTx = new SignedTransaction(
rawTx,
sender.signTransactionWithAuthenticator(rawTx)
);
PendingTransaction pendingTx = client.submitTransaction(signedTx);
System.out.println("Transaction hash: " + pendingTx.getHash());
// Wait for completion
Transaction completedTx = client.waitForTransaction(pendingTx.getHash());
System.out.println("Success: " + completedTx.isSuccess());

To transfer other coin types (not just APT), specify the coin type as a type argument:

// Transfer USDC (example)
TypeTag coinType = new TypeTag.Struct(new StructTag(
AccountAddress.fromHex("0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa"),
new Identifier("asset"),
new Identifier("USDC"),
Arrays.asList()
));
ModuleId moduleId = new ModuleId(
AccountAddress.fromHex("0x1"),
new Identifier("coin")
);
TransactionPayload payload = new EntryFunctionPayload(
moduleId,
new Identifier("transfer"),
Arrays.asList(coinType), // type argument for coin type
Arrays.asList(
new TransactionArgument.AccountAddress(receiver.getAccountAddress()),
new TransactionArgument.U64(1_000_000L)
)
);

You can call any public entry function on deployed Move modules.

// Call a custom module function
// Format: <account_address>::<module_name>::<function_name>
ModuleId moduleId = new ModuleId(
AccountAddress.fromHex("0x123..."), // module owner address
new Identifier("my_module")
);
TransactionPayload payload = new EntryFunctionPayload(
moduleId,
new Identifier("my_function"),
Arrays.asList(), // type arguments
Arrays.asList(
new TransactionArgument.U64(100L),
new TransactionArgument.Bool(true),
new TransactionArgument.String("hello")
)
);
// Build and submit transaction as shown above

The SDK supports various argument types:

// Numbers
new TransactionArgument.U8((byte) 42)
new TransactionArgument.U16((short) 1000)
new TransactionArgument.U32(100000)
new TransactionArgument.U64(1000000L)
new TransactionArgument.U128(new BigInteger("1000000000"))
new TransactionArgument.U256(new BigInteger("1000000000000"))
// Boolean
new TransactionArgument.Bool(true)
// String
new TransactionArgument.String("hello world")
// Address
new TransactionArgument.AccountAddress(
AccountAddress.fromHex("0x1")
)
// Vectors
new TransactionArgument.U8Vector(new byte[]{1, 2, 3})
new TransactionArgument.Vector(Arrays.asList(
new TransactionArgument.U64(100L),
new TransactionArgument.U64(200L)
))

Multi-signature accounts can sign transactions that require multiple approvals.

import com.aptoslabs.japtos.account.MultiEd25519Account;
// Create multi-sig account (2-of-3)
Ed25519Account account1 = Ed25519Account.generate();
Ed25519Account account2 = Ed25519Account.generate();
Ed25519Account account3 = Ed25519Account.generate();
List<Ed25519PrivateKey> privateKeys = Arrays.asList(
account1.getPrivateKey(),
account2.getPrivateKey(),
account3.getPrivateKey()
);
MultiEd25519Account multiSigAccount = MultiEd25519Account.fromPrivateKeys(
privateKeys,
2 // threshold: 2 signatures required
);
// Fund multi-sig account
client.fundAccount(multiSigAccount.getAccountAddress(), 100_000_000);
// Build transaction (same as single-key)
RawTransaction rawTx = new RawTransaction(
multiSigAccount.getAccountAddress(),
sequenceNumber,
payload,
200000L,
100L,
System.currentTimeMillis() / 1000 + 600,
chainId
);
// Sign with multi-sig account (automatically uses threshold signatures)
SignedTransaction signedTx = new SignedTransaction(
rawTx,
multiSigAccount.signTransactionWithAuthenticator(rawTx)
);
// Submit normally
PendingTransaction pendingTx = client.submitTransaction(signedTx);

Before submitting a transaction, you can simulate it to estimate gas costs and check for errors.

// Build raw transaction
RawTransaction rawTx = new RawTransaction(
sender.getAccountAddress(),
sequenceNumber,
payload,
200000L,
100L,
System.currentTimeMillis() / 1000 + 600,
chainId
);
// Simulate transaction (doesn't require signing)
SimulationResult simulation = client.simulateTransaction(rawTx, sender);
System.out.println("Gas used: " + simulation.getGasUsed());
System.out.println("Success: " + simulation.isSuccess());
if (!simulation.isSuccess()) {
System.out.println("Error: " + simulation.getVmStatus());
}

The Java SDK automatically handles BCS serialization for transactions. However, you can also use BCS directly for custom serialization needs.

import com.aptoslabs.japtos.bcs.Serializer;
import com.aptoslabs.japtos.bcs.Deserializer;
// Serialize data
Serializer serializer = new Serializer();
serializer.serializeU64(123456L);
serializer.serializeString("hello");
byte[] bytes = serializer.toBytes();
// Deserialize data
Deserializer deserializer = new Deserializer(bytes);
long value = deserializer.deserializeU64();
String text = deserializer.deserializeString();

Gas costs depend on:

  • Transaction complexity
  • Storage usage
  • Network congestion
// Typical gas values for common transactions
long simpleTransferGas = 200000L; // ~0.002 APT at 100 gas price
long functionCallGas = 500000L; // ~0.005 APT at 100 gas price
long modulePublishGas = 2000000L; // ~0.02 APT at 100 gas price
// Gas unit price (in octas)
long gasUnitPrice = 100L; // Standard price
// Calculate max fee
long maxFee = maxGasAmount * gasUnitPrice;
System.out.println("Max fee: " + maxFee + " octas");
RawTransaction rawTx = new RawTransaction(
sender.getAccountAddress(),
sequenceNumber,
payload,
500000L, // maxGasAmount - increase for complex transactions
150L, // gasUnitPrice - increase for faster processing
System.currentTimeMillis() / 1000 + 600,
chainId
);

After submission, you can check transaction status:

// Submit transaction
PendingTransaction pendingTx = client.submitTransaction(signedTx);
// Wait with timeout (blocks until completion)
Transaction completedTx = client.waitForTransaction(
pendingTx.getHash(),
30 // timeout in seconds (optional)
);
// Check status
if (completedTx.isSuccess()) {
System.out.println("Transaction succeeded!");
System.out.println("Gas used: " + completedTx.getGasUsed());
} else {
System.out.println("Transaction failed!");
System.out.println("Error: " + completedTx.getVmStatus());
}

Always implement proper error handling for transactions:

try {
// Build transaction
RawTransaction rawTx = new RawTransaction(
sender.getAccountAddress(),
sequenceNumber,
payload,
200000L,
100L,
System.currentTimeMillis() / 1000 + 600,
chainId
);
// Sign and submit
SignedTransaction signedTx = new SignedTransaction(
rawTx,
sender.signTransactionWithAuthenticator(rawTx)
);
PendingTransaction pendingTx = client.submitTransaction(signedTx);
// Wait for completion
Transaction completedTx = client.waitForTransaction(pendingTx.getHash());
if (!completedTx.isSuccess()) {
throw new RuntimeException("Transaction failed: " + completedTx.getVmStatus());
}
System.out.println("Transaction successful!");
} catch (SequenceNumberException e) {
System.err.println("Incorrect sequence number: " + e.getMessage());
} catch (InsufficientBalanceException e) {
System.err.println("Insufficient balance: " + e.getMessage());
} catch (TransactionException e) {
System.err.println("Transaction error: " + e.getMessage());
} catch (Exception e) {
System.err.println("Unexpected error: " + e.getMessage());
}

Here’s a complete, production-ready transaction example:

import com.aptoslabs.japtos.account.Ed25519Account;
import com.aptoslabs.japtos.api.AptosConfig;
import com.aptoslabs.japtos.client.AptosClient;
import com.aptoslabs.japtos.transaction.*;
import com.aptoslabs.japtos.types.*;
import java.util.Arrays;
public class TransactionExample {
public static void main(String[] args) {
try {
// Setup
AptosConfig config = AptosConfig.builder()
.network(AptosConfig.Network.DEVNET)
.build();
AptosClient client = new AptosClient(config);
// Load or create accounts
Ed25519Account sender = Ed25519Account.generate();
Ed25519Account receiver = Ed25519Account.generate();
// Fund accounts
client.fundAccount(sender.getAccountAddress(), 100_000_000);
// Build transfer payload
ModuleId moduleId = new ModuleId(
AccountAddress.fromHex("0x1"),
new Identifier("aptos_account")
);
TransactionPayload payload = new EntryFunctionPayload(
moduleId,
new Identifier("transfer"),
Arrays.asList(),
Arrays.asList(
new TransactionArgument.AccountAddress(receiver.getAccountAddress()),
new TransactionArgument.U64(1_000_000L)
)
);
// Get account sequence number and chain ID
long sequenceNumber = client.getAccountSequenceNumber(
sender.getAccountAddress()
);
int chainId = client.getChainId();
// Build raw transaction
RawTransaction rawTx = new RawTransaction(
sender.getAccountAddress(),
sequenceNumber,
payload,
200000L,
100L,
System.currentTimeMillis() / 1000 + 600,
chainId
);
// Simulate before submitting
SimulationResult simulation = client.simulateTransaction(rawTx, sender);
System.out.println("Estimated gas: " + simulation.getGasUsed());
if (!simulation.isSuccess()) {
throw new RuntimeException("Simulation failed: " + simulation.getVmStatus());
}
// Sign transaction
SignedTransaction signedTx = new SignedTransaction(
rawTx,
sender.signTransactionWithAuthenticator(rawTx)
);
// Submit transaction
PendingTransaction pendingTx = client.submitTransaction(signedTx);
System.out.println("Transaction submitted: " + pendingTx.getHash());
// Wait for completion
Transaction completedTx = client.waitForTransaction(pendingTx.getHash());
if (completedTx.isSuccess()) {
System.out.println("Transaction succeeded!");
System.out.println("Gas used: " + completedTx.getGasUsed());
} else {
System.err.println("Transaction failed: " + completedTx.getVmStatus());
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
}
  1. Always simulate transactions before submitting to check for errors
  2. Set appropriate gas limits - not too low (fails) or too high (wastes limits)
  3. Handle sequence numbers carefully - use client.getAccountSequenceNumber() for each transaction
  4. Implement retry logic for network failures
  5. Check transaction status after submission
  6. Set reasonable expiration times (typically 5-10 minutes)
  7. Use appropriate network (devnet for testing, mainnet for production)