---
title: "Aptos dApp Development Guide"
sidebar_position: 3
tags: [Aptos, dApp, development, frontend, TypeScript]
keywords: [Aptos dApp, blockchain development, TypeScript SDK, wallet integration, Move contracts]
description: "Complete guide to building modern decentralized applications on Aptos blockchain using the latest @aptos-labs/ts-sdk and best practices."
last_updated: 2026-10-05
---

# Aptos dApp Development Guide

This comprehensive guide covers building modern decentralized applications (dApps) on the Aptos blockchain using the latest @aptos-labs/ts-sdk and development best practices for 2025.

## Prerequisites

- **Node.js 18+** for running the development environment
- **Basic TypeScript/JavaScript knowledge** for frontend development
- **Understanding of React** for UI components (optional but recommended)
- **Aptos wallet installed** (Petra, Pontem, or other Aptos-compatible wallets)

## Core Technologies

### Essential Packages

```bash
# Core Aptos SDK
npm install @aptos-labs/ts-sdk

# React integration (optional)
npm install react react-dom @types/react @types/react-dom

# State management
npm install @tanstack/react-query

# UI components (optional)
npm install @aptos-labs/wallet-adapter-react
```

### Package Overview

- **@aptos-labs/ts-sdk**: Core SDK for blockchain interactions
- **@aptos-labs/wallet-adapter-react**: React hooks for wallet integration
- **@tanstack/react-query**: State management for async data

## Basic Setup

### 1. Initialize Aptos Client

```typescript
import { Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk';

// Initialize for different networks
const config = new AptosConfig({ 
  network: Network.MAINNET // Network.TESTNET, Network.DEVNET
});
const aptos = new Aptos(config);

// Using bex.co endpoint
const bexConfig = new AptosConfig({
  fullnode: 'https://aptos-mainnet.bex.co/<access_key>',
  network: Network.MAINNET,
});
const bexAptos = new Aptos(bexConfig);
```

### 2. React dApp Setup with Wallet Integration

```typescript
// App.tsx
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { WalletProvider } from '@aptos-labs/wallet-adapter-react';
import { PetraWallet } from 'petra-plugin-wallet-adapter';
import { PontemWallet } from '@pontem/wallet-adapter-plugin';

const queryClient = new QueryClient();

const wallets = [
  new PetraWallet(),
  new PontemWallet(),
];

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <WalletProvider wallets={wallets} autoConnect={true}>
        <MyDApp />
      </WalletProvider>
    </QueryClientProvider>
  );
}

export default App;
```

### 3. Wallet Connection Component

```typescript
// WalletConnection.tsx
import React from 'react';
import { useWallet } from '@aptos-labs/wallet-adapter-react';

export function WalletConnection() {
  const { 
    connect, 
    disconnect, 
    account, 
    connected, 
    wallets,
    wallet
  } = useWallet();

  const handleConnect = async (walletName: string) => {
    try {
      await connect(walletName);
    } catch (error) {
      console.error('Failed to connect wallet:', error);
    }
  };

  const handleDisconnect = async () => {
    try {
      await disconnect();
    } catch (error) {
      console.error('Failed to disconnect wallet:', error);
    }
  };

  if (connected && account) {
    return (
      <div className="wallet-connected">
        <p>Connected: {account.address}</p>
        <p>Wallet: {wallet?.name}</p>
        <button onClick={handleDisconnect}>Disconnect</button>
      </div>
    );
  }

  return (
    <div className="wallet-selection">
      <h3>Connect Your Wallet</h3>
      {wallets.map((wallet) => (
        <button
          key={wallet.name}
          onClick={() => handleConnect(wallet.name)}
          disabled={!wallet.readyState}
        >
          Connect {wallet.name}
        </button>
      ))}
    </div>
  );
}
```

## Working with Transactions

### Basic APT Transfer

```typescript
import { Account, Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk';
import { useWallet } from '@aptos-labs/wallet-adapter-react';

export function TransferAPT() {
  const { account, signAndSubmitTransaction } = useWallet();
  const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

  const transferAPT = async (recipient: string, amount: number) => {
    if (!account) {
      throw new Error('Wallet not connected');
    }

    try {
      const transaction = await aptos.transaction.build.simple({
        sender: account.address,
        data: {
          function: '0x1::aptos_account::transfer',
          functionArguments: [
            recipient, // recipient address
            amount * 100000000, // amount in Octas (1 APT = 100,000,000 Octas)
          ],
        },
      });

      const response = await signAndSubmitTransaction({
        transaction,
      });

      // Wait for transaction confirmation
      const executedTransaction = await aptos.waitForTransaction({
        transactionHash: response.hash,
      });

      console.log('Transfer successful:', executedTransaction);
      return executedTransaction;
    } catch (error) {
      console.error('Transfer failed:', error);
      throw error;
    }
  };

  return (
    <div>
      <button onClick={() => transferAPT('0x...', 1)}>
        Send 1 APT
      </button>
    </div>
  );
}
```

### Multi-Agent Transactions

```typescript
export function MultiAgentTransaction() {
  const { account, signAndSubmitTransaction } = useWallet();
  const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

  const createMultiAgentTx = async (secondaryAccount: string) => {
    if (!account) throw new Error('Wallet not connected');

    try {
      const transaction = await aptos.transaction.build.multiAgent({
        sender: account.address,
        secondarySignerAddresses: [secondaryAccount],
        data: {
          function: '0x1::aptos_account::transfer',
          functionArguments: [
            account.address, // Transfer back to primary account
            1000000, // 0.01 APT
          ],
        },
      });

      const response = await signAndSubmitTransaction({
        transaction,
      });

      return await aptos.waitForTransaction({
        transactionHash: response.hash,
      });
    } catch (error) {
      console.error('Multi-agent transaction failed:', error);
      throw error;
    }
  };

  return (
    <button onClick={() => createMultiAgentTx('0x...')}>
      Create Multi-Agent Transaction
    </button>
  );
}
```

## Data Fetching Patterns

### Account Information and Balance

```typescript
import { useQuery } from '@tanstack/react-query';
import { Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk';

const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

export function AccountBalance({ address }: { address: string }) {
  const { data: balance, isLoading, error } = useQuery({
    queryKey: ['balance', address],
    queryFn: async () => {
      const resources = await aptos.getAccountResources({
        accountAddress: address,
      });
      
      const coinStore = resources.find(
        (resource) => resource.type === '0x1::coin::CoinStore&lt;0x1::aptos_coin::AptosCoin>'
      );
      
      if (coinStore?.data) {
        return (coinStore.data as any).coin.value;
      }
      return '0';
    },
    enabled: !!address,
    refetchInterval: 10000, // Refresh every 10 seconds
  });

  if (isLoading) return <div>Loading balance...</div>;
  if (error) return <div>Error loading balance</div>;

  const aptBalance = balance ? Number(balance) / 100000000 : 0;

  return (
    <div>
      Balance: {aptBalance.toFixed(8)} APT
    </div>
  );
}
```

### Account Resources and Modules

```typescript
export function AccountResources({ address }: { address: string }) {
  const { data: resources } = useQuery({
    queryKey: ['resources', address],
    queryFn: () => aptos.getAccountResources({ accountAddress: address }),
    enabled: !!address,
  });

  const { data: modules } = useQuery({
    queryKey: ['modules', address],
    queryFn: () => aptos.getAccountModules({ accountAddress: address }),
    enabled: !!address,
  });

  return (
    <div>
      <h3>Account Resources</h3>
      <div>Total Resources: {resources?.length || 0}</div>
      <div>Total Modules: {modules?.length || 0}</div>
      
      {resources?.slice(0, 5).map((resource, index) => (
        <div key={index}>
          <strong>Type:</strong> {resource.type}
        </div>
      ))}
    </div>
  );
}
```

### Transaction History

```typescript
export function TransactionHistory({ address }: { address: string }) {
  const { data: transactions, isLoading } = useQuery({
    queryKey: ['transactions', address],
    queryFn: () => aptos.getAccountTransactions({
      accountAddress: address,
      options: {
        limit: 10,
        orderBy: [{ transaction_version: 'desc' }],
      },
    }),
    enabled: !!address,
    refetchInterval: 30000, // Refresh every 30 seconds
  });

  if (isLoading) return <div>Loading transactions...</div>;

  return (
    <div>
      <h3>Recent Transactions</h3>
      {transactions?.map((tx) => (
        <div key={tx.version} className="transaction-item">
          <div>Version: {tx.version}</div>
          <div>Hash: {tx.hash}</div>
          <div>Type: {tx.type}</div>
          <div>Success: {tx.success ? 'Yes' : 'No'}</div>
        </div>
      ))}
    </div>
  );
}
```

## Event Handling and Monitoring

### Event Listening with React

```typescript
import { useEffect, useState } from 'react';
import { Event } from '@aptos-labs/ts-sdk';

export function EventMonitor({ eventType }: { eventType: string }) {
  const [events, setEvents] = useState<Event[]>([]);
  const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

  useEffect(() => {
    const fetchEvents = async () => {
      try {
        const eventData = await aptos.getEvents({
          eventType,
          options: {
            limit: 10,
            orderBy: [{ transaction_version: 'desc' }],
          },
        });
        
        setEvents(eventData);
      } catch (error) {
        console.error('Error fetching events:', error);
      }
    };

    fetchEvents();
    
    // Poll for new events
    const interval = setInterval(fetchEvents, 5000);
    return () => clearInterval(interval);
  }, [eventType]);

  return (
    <div>
      <h3>Events: {eventType}</h3>
      {events.map((event, index) => (
        <div key={index} className="event-item">
          <div>Version: {event.transaction_version}</div>
          <div>Sequence: {event.sequence_number}</div>
          <div>Data: {JSON.stringify(event.data)}</div>
        </div>
      ))}
    </div>
  );
}
```

### Custom Hook for Event Subscription

```typescript
import { useQuery } from '@tanstack/react-query';

export function useEvents(eventType: string, enabled: boolean = true) {
  const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

  return useQuery({
    queryKey: ['events', eventType],
    queryFn: () => aptos.getEvents({
      eventType,
      options: {
        limit: 20,
        orderBy: [{ transaction_version: 'desc' }],
      },
    }),
    enabled,
    refetchInterval: 5000,
  });
}
```

## Error Handling and Validation

### Comprehensive Error Handling

```typescript
import { AptosApiError } from '@aptos-labs/ts-sdk';

export function RobustTransaction() {
  const { signAndSubmitTransaction } = useWallet();
  const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

  const executeTransaction = async (transaction: any) => {
    try {
      // Simulate transaction first
      const simulation = await aptos.transaction.simulate.simple({
        signerPublicKey: account.publicKey,
        transaction,
      });

      if (!simulation[0].success) {
        throw new Error(`Simulation failed: ${simulation[0].vm_status}`);
      }

      // Execute the transaction
      const response = await signAndSubmitTransaction({ transaction });
      
      // Wait for confirmation
      const confirmedTx = await aptos.waitForTransaction({
        transactionHash: response.hash,
        options: {
          checkSuccess: true,
        },
      });

      return confirmedTx;
    } catch (error) {
      if (error instanceof AptosApiError) {
        console.error('Aptos API Error:', error.message);
        console.error('Status:', error.status);
        console.error('Data:', error.data);
      } else if (error.message.includes('User rejected')) {
        console.log('User cancelled transaction');
      } else {
        console.error('Unexpected error:', error);
      }
      throw error;
    }
  };

  return (
    <button onClick={() => executeTransaction(/* transaction */)}>
      Execute Transaction
    </button>
  );
}
```

### Input Validation

```typescript
import { AccountAddress } from '@aptos-labs/ts-sdk';

export function validateAptosAddress(address: string): boolean {
  try {
    AccountAddress.fromString(address);
    return true;
  } catch {
    return false;
  }
}

export function validateAmount(amount: string): { isValid: boolean; error?: string } {
  const numAmount = parseFloat(amount);
  
  if (isNaN(numAmount)) {
    return { isValid: false, error: 'Invalid number format' };
  }
  
  if (numAmount <= 0) {
    return { isValid: false, error: 'Amount must be positive' };
  }
  
  if (numAmount > 1000000) {
    return { isValid: false, error: 'Amount too large' };
  }
  
  return { isValid: true };
}
```

## Performance Optimization

### Batch Requests

```typescript
export function BatchedAccountData({ addresses }: { addresses: string[] }) {
  const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

  const { data: accountData } = useQuery({
    queryKey: ['batchedAccounts', addresses],
    queryFn: async () => {
      // Batch multiple account queries
      const accountPromises = addresses.map(address => 
        aptos.getAccount({ accountAddress: address })
          .catch(error => ({ error, address }))
      );
      
      const results = await Promise.allSettled(accountPromises);
      
      return results.map((result, index) => ({
        address: addresses[index],
        data: result.status === 'fulfilled' ? result.value : null,
        error: result.status === 'rejected' ? result.reason : null,
      }));
    },
    enabled: addresses.length > 0,
  });

  return (
    <div>
      <h3>Account Data</h3>
      {accountData?.map((item, index) => (
        <div key={index}>
          <strong>{item.address}:</strong>
          {item.error ? (
            <span> Error loading account</span>
          ) : (
            <span> Sequence: {item.data?.sequence_number}</span>
          )}
        </div>
      ))}
    </div>
  );
}
```

### Caching and Memoization

```typescript
import { useMemo } from 'react';

export function ProcessedTransactions({ transactions }: { transactions: any[] }) {
  const processedData = useMemo(() => {
    return transactions
      .filter(tx => tx.success)
      .map(tx => ({
        hash: tx.hash,
        version: tx.version,
        timestamp: new Date(Number(tx.timestamp) / 1000),
        gasUsed: tx.gas_used,
      }))
      .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
  }, [transactions]);

  return (
    <div>
      {processedData.map(tx => (
        <div key={tx.hash}>
          <div>Hash: {tx.hash}</div>
          <div>Gas Used: {tx.gasUsed}</div>
          <div>Time: {tx.timestamp.toLocaleString()}</div>
        </div>
      ))}
    </div>
  );
}
```

## Testing Strategies

### Unit Testing

```typescript
// __tests__/aptosUtils.test.ts
import { describe, it, expect } from '@jest/globals';
import { validateAptosAddress, validateAmount } from '../utils/validation';

describe('Aptos Utilities', () => {
  describe('validateAptosAddress', () => {
    it('should validate correct Aptos addresses', () => {
      expect(validateAptosAddress('0x1')).toBe(true);
      expect(validateAptosAddress('0x1::coin::CoinStore')).toBe(false);
    });

    it('should reject invalid addresses', () => {
      expect(validateAptosAddress('invalid')).toBe(false);
      expect(validateAptosAddress('')).toBe(false);
    });
  });

  describe('validateAmount', () => {
    it('should validate positive numbers', () => {
      expect(validateAmount('1.5')).toEqual({ isValid: true });
      expect(validateAmount('100')).toEqual({ isValid: true });
    });

    it('should reject invalid amounts', () => {
      expect(validateAmount('-1')).toEqual({ 
        isValid: false, 
        error: 'Amount must be positive' 
      });
      expect(validateAmount('abc')).toEqual({ 
        isValid: false, 
        error: 'Invalid number format' 
      });
    });
  });
});
```

### Integration Testing

```typescript
// __tests__/integration.test.ts
import { Aptos, AptosConfig, Network, Account } from '@aptos-labs/ts-sdk';

describe('Aptos Integration Tests', () => {
  let aptos: Aptos;
  let account: Account;

  beforeAll(async () => {
    aptos = new Aptos(new AptosConfig({ network: Network.DEVNET }));
    account = Account.generate();
    
    // Fund account for testing
    await aptos.fundAccount({ 
      accountAddress: account.accountAddress, 
      amount: 100000000 
    });
  });

  it('should transfer APT successfully', async () => {
    const recipient = Account.generate();
    
    const transaction = await aptos.transaction.build.simple({
      sender: account.accountAddress,
      data: {
        function: '0x1::aptos_account::transfer',
        functionArguments: [recipient.accountAddress, 1000000],
      },
    });

    const response = await aptos.signAndSubmitTransaction({
      signer: account,
      transaction,
    });

    const confirmedTx = await aptos.waitForTransaction({
      transactionHash: response.hash,
    });

    expect(confirmedTx.success).toBe(true);
  });
});
```

## Deployment Configuration

### Environment-Specific Setup

```typescript
// config/aptos.ts
import { AptosConfig, Network } from '@aptos-labs/ts-sdk';

export const getAptosConfig = () => {
  const environment = process.env.NODE_ENV;
  
  switch (environment) {
    case 'production':
      return new AptosConfig({
        network: Network.MAINNET,
        fullnode: process.env.NEXT_PUBLIC_APTOS_MAINNET_URL,
      });
    case 'staging':
      return new AptosConfig({
        network: Network.TESTNET,
        fullnode: process.env.NEXT_PUBLIC_APTOS_TESTNET_URL,
      });
    default:
      return new AptosConfig({
        network: Network.DEVNET,
      });
  }
};
```

### Production Monitoring

```typescript
// utils/monitoring.ts
export function logTransaction(hash: string, status: 'success' | 'failure', error?: any) {
  const logData = {
    transactionHash: hash,
    status,
    timestamp: new Date().toISOString(),
    error: error?.message,
  };

  // Log to console in development
  if (process.env.NODE_ENV === 'development') {
    console.log('Transaction log:', logData);
  }

  // Send to monitoring service in production
  if (process.env.NODE_ENV === 'production') {
    // Example: send to DataDog, Sentry, etc.
    // monitoringService.log('aptos_transaction', logData);
  }
}
```

## Best Practices Summary

1. **Use TypeScript** for better type safety and developer experience
2. **Implement proper error handling** for all blockchain interactions
3. **Validate inputs** before sending transactions
4. **Simulate transactions** before execution when possible
5. **Use React Query** for efficient data fetching and caching
6. **Handle wallet connection states** gracefully
7. **Test thoroughly** with both unit and integration tests
8. **Monitor transactions** in production environments
9. **Secure API endpoints** using environment variables

## Next Steps

- Explore the [Aptos Move Integration Guide](./aptos-move-integration.md) for smart contract interactions
- Learn about [Multi-Chain dApp Architecture](../multi-chain-architecture.md) for cross-chain applications
- Implement proper security measures for production deployment

## Resources

- [Aptos TypeScript SDK Documentation](https://aptos-labs.github.io/aptos-ts-sdk/)
- [Aptos Developer Documentation](https://aptos.dev/)
- [Aptos Wallet Adapter](https://github.com/aptos-labs/aptos-wallet-adapter)
- bex.co Aptos API