Skip to content

Your First Transaction

Transactions are the fundamental way to change data on the Aptos blockchain. Think of them like sending a package: you need to specify what you’re sending, who it’s going to, and then track it until delivery is confirmed. In blockchain terms, transactions allow you to transfer coins, call smart contract functions, and update on-chain state.

This tutorial will guide you through creating and submitting your first transaction on the Aptos blockchain. You’ll learn how to:

  1. Set up your development environment
  2. Create test accounts and fund them
  3. Build a transaction to transfer coins
  4. Simulate the transaction to estimate costs
  5. Sign and submit the transaction
  6. Verify the transaction was executed successfully

Before we can create transactions, we need to set up our development environment with the necessary tools and SDKs.

  1. Install the TypeScript SDK

    Install the TypeScript SDK using your preferred package manager:

    Terminal window
    npm install @aptos-labs/ts-sdk
  2. Create a project directory

    Create a new directory for your project:

    Terminal window
    mkdir my-first-transaction
    cd my-first-transaction
  3. Create a new file

    Create a new file named transaction.ts:

    Terminal window
    touch transaction.ts

In blockchain, all transactions must come from an account. Let’s create two test accounts: one to send coins (Alice) and one to receive them (Bob).

  1. Set up the client

    First, we need to initialize the Aptos client that will connect to the blockchain. Open transaction.ts in your editor and add:

    import {
    Account,
    Aptos,
    AptosConfig,
    Network,
    } from "@aptos-labs/ts-sdk";
    async function main() {
    // Initialize the Aptos client
    const config = new AptosConfig({ network: Network.DEVNET });
    const aptos = new Aptos(config);
    console.log("Connected to Aptos devnet");
    // More code will go here
    }
    main().catch(console.error);
  2. Generate accounts

    Add this code inside your main() function to create two accounts - Alice (sender) and Bob (receiver):

    // Generate two accounts
    const alice = Account.generate();
    const bob = Account.generate();
    console.log("=== Addresses ===");
    console.log(`Alice's address: ${alice.accountAddress}`);
    console.log(`Bob's address: ${bob.accountAddress}`);
  3. Fund the accounts

    Add this code after generating the accounts to get test coins from the faucet:

    // Fund the accounts with test APT from the devnet faucet
    console.log("\n=== Funding accounts ===");
    await aptos.fundAccount({
    accountAddress: alice.accountAddress,
    amount: 100_000_000, // 1 APT = 100,000,000 octas
    });
    console.log("Accounts funded successfully");
    // Check initial balances
    const aliceBalance = await aptos.getAccountAPTAmount({
    accountAddress: alice.accountAddress,
    });
    const bobBalance = await aptos.getAccountAPTAmount({
    accountAddress: bob.accountAddress,
    });
    console.log("\n=== Initial Balances ===");
    console.log(`Alice: ${aliceBalance} octas`);
    console.log(`Bob: ${bobBalance} octas`);
  4. Run the code

    Let’s test our code so far:

    Terminal window
    npx ts-node transaction.ts

    You should see output similar to:

    Connected to Aptos devnet
    === Addresses ===
    Alice's address: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa
    Bob's address: 0x7af2d6c93a2feafc9b69b5e8ad9d6b513b260f62f23f3a384a3a2e4a84694a9b
    === Funding accounts ===
    Accounts funded successfully
    === Initial Balances ===
    Alice: 100000000 octas
    Bob: 0 octas

Now that we have funded accounts, let’s create a transaction to transfer coins from Alice to Bob. This is like filling out a form specifying what you want to send and to whom.

  1. Understand transaction structure

    A transaction in Aptos has several key components:

    1. Sender: The account initiating the transaction (Alice)
    2. Function: The on-chain function to call (in this case, a coin transfer)
    3. Arguments: Data needed by the function (recipient address and amount)
    4. Gas parameters: Maximum gas amount and gas unit price
    5. Expiration time: When the transaction is no longer valid if not executed
    6. Sequence number: A counter that prevents replay attacks
  2. Build the transaction

    Let’s add code to build a transaction that transfers 1000 octas from Alice to Bob:

    Add this code to your main() function:

    // 1. Build the transaction
    console.log("\n=== 1. Building the transaction ===");
    const transaction = await aptos.transaction.build.simple({
    sender: alice.accountAddress,
    data: {
    function: "0x1::aptos_account::transfer",
    functionArguments: [bob.accountAddress, 1000], // Transfer 1000 octas
    },
    });
    console.log("Transaction built successfully");
    // Access transaction details from the raw transaction
    const rawTxn = transaction.rawTransaction;
    console.log(`Sender: ${rawTxn.sender}`);
    console.log(`Sequence Number: ${rawTxn.sequence_number}`);
    console.log(`Max Gas Amount: ${rawTxn.max_gas_amount}`);
    console.log(`Gas Unit Price: ${rawTxn.gas_unit_price}`);
    console.log(`Expiration Timestamp: ${new Date(Number(rawTxn.expiration_timestamp_secs) * 1000).toISOString()}`);

Before submitting a transaction, it’s wise to simulate it first to estimate the gas cost. This is like checking shipping costs before sending a package.

  1. Simulate the transaction

    Add this code after building the transaction:

    // 2. Simulate the transaction
    console.log("\n=== 2. Simulating the transaction ===");
    const [simulationResult] = await aptos.transaction.simulate.simple({
    signerPublicKey: alice.publicKey,
    transaction,
    });
    const gasUsed = parseInt(simulationResult.gas_used);
    const gasUnitPrice = parseInt(simulationResult.gas_unit_price);
    console.log(`Estimated gas units: ${gasUsed}`);
    console.log(`Estimated gas cost: ${gasUsed * gasUnitPrice} octas`);
    console.log(`Transaction would ${simulationResult.success ? "succeed" : "fail"}`);

Now that we’ve built and simulated the transaction, we need to sign it with Alice’s private key and submit it to the blockchain.

  1. Sign the transaction

    Signing proves that Alice authorized this transaction:

    Add this code after simulating the transaction:

    // 3. Sign the transaction
    console.log("\n=== 3. Signing the transaction ===");
    const senderAuthenticator = aptos.transaction.sign({
    signer: alice,
    transaction,
    });
    console.log("Transaction signed successfully");
  2. Submit the transaction

    Add this code after signing the transaction to submit the signed transaction to the blockchain:

    // 4. Submit the transaction
    console.log("\n=== 4. Submitting the transaction ===");
    const pendingTransaction = await aptos.transaction.submit.simple({
    transaction,
    senderAuthenticator,
    });
    console.log(`Transaction submitted with hash: ${pendingTransaction.hash}`);

After submitting a transaction, we need to wait for it to be processed by the blockchain. This is like waiting for a package to be delivered.

  1. Wait for transaction completion

    Add this code after submitting the transaction:

    // 5. Wait for the transaction to complete
    console.log("\n=== 5. Waiting for transaction completion ===");
    const txnResult = await aptos.waitForTransaction({
    transactionHash: pendingTransaction.hash,
    });
    console.log(`Transaction completed with status: ${txnResult.success ? "SUCCESS" : "FAILURE"}`);
    // If you want to see more details about the transaction:
    console.log(`VM Status: ${txnResult.vm_status}`);
    console.log(`Gas used: ${txnResult.gas_used}`);
  2. Verify the results

    Add this code after waiting for the transaction to check the balances and confirm the transfer worked:

    // Check final balances
    const aliceFinalBalance = await aptos.getAccountAPTAmount({
    accountAddress: alice.accountAddress,
    });
    const bobFinalBalance = await aptos.getAccountAPTAmount({
    accountAddress: bob.accountAddress,
    });
    console.log("\n=== Final Balances ===");
    console.log(`Alice: ${aliceFinalBalance} octas (spent ${aliceBalance - aliceFinalBalance} octas on transfer and gas)`);
    console.log(`Bob: ${bobFinalBalance} octas (received 1000 octas)`);
  3. Run the complete code

    Terminal window
    npx ts-node transaction.ts

    You should see output similar to:

    Connected to Aptos devnet
    === Addresses ===
    Alice's address: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa
    Bob's address: 0x7af2d6c93a2feafc9b69b5e8ad9d6b513b260f62f23f3a384a3a2e4a84694a9b
    === Funding accounts ===
    Accounts funded successfully
    === Initial Balances ===
    Alice: 100000000 octas
    Bob: 0 octas
    === 1. Building the transaction ===
    Transaction built successfully
    Sender: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa
    Sequence Number: 0
    Max Gas Amount: 20000
    Gas Unit Price: 100
    Expiration Timestamp: 2025-03-05T22:59:21.000Z
    === 2. Simulating the transaction ===
    Estimated gas units: 146
    Estimated gas cost: 14600 octas
    Transaction would succeed
    === 3. Signing the transaction ===
    Transaction signed successfully
    === 4. Submitting the transaction ===
    Transaction submitted with hash: 0x3a8a3e34a1c64ad9d7636a3a827b7ec3bb12d73825b36fa06d425c5a3b42cccc
    === 5. Waiting for transaction completion ===
    Transaction completed with status: SUCCESS
    VM Status: Executed successfully
    Gas used: 146
    === Final Balances ===
    Alice: 99984400 octas (spent 15600 octas on transfer and gas)
    Bob: 1000 octas (received 1000 octas)

7. (Optional) Explore Your Transaction On-Chain

Section titled “7. (Optional) Explore Your Transaction On-Chain”

Now that you’ve successfully executed a transaction, you can explore it on the Aptos Explorer. This will help you understand how transactions are recorded on the blockchain and what information is publicly available.

  1. Copy your transaction hash

    From your terminal output, copy the transaction hash that was printed after submission. It looks something like this:

    Transaction submitted with hash: 0x3a8a3e34a1c64ad9d7636a3a827b7ec3bb12d73825b36fa06d425c5a3b42cccc
  2. Open the Aptos Explorer

    Go to the Aptos Explorer.

  3. Ensure you are on Devnet network

    Look for “Devnet” in the top right corner, or switch networks by clicking the dropdown and selecting Devnet.

    Switching to Devnet network in Aptos Explorer

  4. Search for your transaction

    Paste your transaction hash into the search bar in the middle of the page.

  5. View the transaction details

    Wait for the results to appear, then click on the transaction hash to view its details.

    You should see information about your transaction, including:

    • Status (should be “Success”)
    • Timestamp
    • Gas used
    • Sender and recipient addresses
    • Amount transferred
  6. Explore further

    From the transaction details page, you can:

    • Click on the sender or recipient addresses to view their account details
    • See the exact changes made to the blockchain state
    • View the transaction payload and arguments

Congratulations! You’ve successfully created and executed your first transaction on the Aptos blockchain. Here are some suggestions for what to explore next:

Learn about more complex transactions:

Explore smart contracts or account basics:

Join the Aptos Discord and share what you’re building!

The complete code samples below combine all the snippets we’ve covered in this tutorial:

import { Account, Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
async function main() {
// Initialize the Aptos client
const config = new AptosConfig({ network: Network.DEVNET });
const aptos = new Aptos(config);
console.log("Connected to Aptos devnet");
// More code will go here
// Generate two accounts
const alice = Account.generate();
const bob = Account.generate();
console.log("=== Addresses ===");
console.log(`Alice's address: ${alice.accountAddress}`);
console.log(`Bob's address: ${bob.accountAddress}`);
// Fund the accounts with test APT from the devnet faucet
console.log("\n=== Funding accounts ===");
await aptos.fundAccount({
accountAddress: alice.accountAddress,
amount: 100_000_000, // 1 APT = 100,000,000 octas
});
await aptos.fundAccount({
accountAddress: bob.accountAddress,
amount: 0, // Bob starts with 0 APT
});
console.log("Accounts funded successfully");
// Check initial balances
const aliceBalance = await aptos.getAccountAPTAmount({
accountAddress: alice.accountAddress,
});
const bobBalance = await aptos.getAccountAPTAmount({
accountAddress: bob.accountAddress,
});
console.log("\n=== Initial Balances ===");
console.log(`Alice: ${aliceBalance} octas`);
console.log(`Bob: ${bobBalance} octas`);
// 1. Build the transaction
console.log("\n=== 1. Building the transaction ===");
const transaction = await aptos.transaction.build.simple({
sender: alice.accountAddress,
data: {
function: "0x1::aptos_account::transfer",
functionArguments: [bob.accountAddress, 1000], // Transfer 1000 octas
},
});
console.log("Transaction built successfully");
// Use type assertion to bypass TypeScript's type checking
const txnAny = transaction as any;
console.log(`Sender: ${alice.accountAddress}`); // Use the known sender address
console.log(`Sequence Number: ${txnAny.sequenceNumber || "N/A"}`);
console.log(`Max Gas Amount: ${txnAny.maxGasAmount || "N/A"}`);
console.log(`Gas Unit Price: ${txnAny.gasUnitPrice || "N/A"}`);
console.log(
`Expiration Timestamp: ${new Date(
Number(txnAny.expirationTimestampSecs || 0) * 1000
).toISOString()}`
);
// 2. Simulate the transaction
console.log("\n=== 2. Simulating the transaction ===");
const [simulationResult] = await aptos.transaction.simulate.simple({
signerPublicKey: alice.publicKey,
transaction,
});
console.log(`Estimated gas units: ${simulationResult.gas_used}`);
console.log(
`Estimated gas cost: ${
Number(simulationResult.gas_used) * Number(simulationResult.gas_unit_price)
} octas`
);
console.log(
`Transaction would ${simulationResult.success ? "succeed" : "fail"}`
);
// 3. Sign the transaction
console.log("\n=== 3. Signing the transaction ===");
const senderAuthenticator = aptos.transaction.sign({
signer: alice,
transaction,
});
console.log("Transaction signed successfully");
// Use type assertion to bypass TypeScript's type checking
const authAny = senderAuthenticator as any;
const signatureStr = typeof authAny.signature === 'string'
? authAny.signature
: JSON.stringify(authAny.signature || '');
console.log(`Signature: ${signatureStr.slice(0, 20)}...`);
// 4. Submit the transaction
console.log("\n=== 4. Submitting the transaction ===");
const pendingTransaction = await aptos.transaction.submit.simple({
transaction,
senderAuthenticator,
});
console.log(`Transaction submitted with hash: ${pendingTransaction.hash}`);
// 5. Wait for the transaction to complete
console.log("\n=== 5. Waiting for transaction completion ===");
const txnResult = await aptos.waitForTransaction({
transactionHash: pendingTransaction.hash,
});
console.log(
`Transaction completed with status: ${
txnResult.success ? "SUCCESS" : "FAILURE"
}`
);
// If you want to see more details about the transaction:
console.log(`VM Status: ${txnResult.vm_status}`);
console.log(`Gas used: ${txnResult.gas_used}`);
// Check final balances
const aliceFinalBalance = await aptos.getAccountAPTAmount({
accountAddress: alice.accountAddress,
});
const bobFinalBalance = await aptos.getAccountAPTAmount({
accountAddress: bob.accountAddress,
});
console.log("\n=== Final Balances ===");
console.log(
`Alice: ${aliceFinalBalance} octas (spent ${
aliceBalance - aliceFinalBalance
} octas on transfer and gas)`
);
console.log(`Bob: ${bobFinalBalance} octas (received 1000 octas)`);
}
main().catch(console.error);