---
title: "Aptos Move Integration Guide"
sidebar_position: 4
tags: [Aptos, Move, smart contracts, TypeScript, integration]
keywords: [Aptos Move, smart contracts, TypeScript integration, resources, events, testing]
description: "Complete guide to integrating Aptos Move smart contracts with TypeScript applications using modern SDK patterns and best practices."
last_updated: 2026-10-05
---

# Aptos Move Integration Guide

This guide covers integrating Aptos Move smart contracts with TypeScript frontends, including contract interaction patterns, resource management, event handling, and testing strategies for 2025.

## Prerequisites

- **Aptos CLI installed** for Move development
- **Understanding of Move language** basics
- **TypeScript/React knowledge** for frontend integration
- **@aptos-labs/ts-sdk** for blockchain interactions

## Move Contract Structure

### Basic Move Module Example

```move
// sources/counter.move
module my_address::counter {
    use std::signer;
    use aptos_framework::event;

    struct Counter has key {
        value: u64,
    }

    #[event]
    struct CounterCreated has drop, store {
        account: address,
        initial_value: u64,
    }

    #[event]
    struct CounterIncremented has drop, store {
        account: address,
        old_value: u64,
        new_value: u64,
    }

    public entry fun initialize_counter(account: &signer) {
        let account_addr = signer::address_of(account);
        
        assert!(!exists<Counter>(account_addr), 1);
        
        let counter = Counter { value: 0 };
        move_to(account, counter);
        
        event::emit(CounterCreated {
            account: account_addr,
            initial_value: 0,
        });
    }

    public entry fun increment(account: &signer) acquires Counter {
        let account_addr = signer::address_of(account);
        let counter = borrow_global_mut<Counter>(account_addr);
        
        let old_value = counter.value;
        counter.value = counter.value + 1;
        
        event::emit(CounterIncremented {
            account: account_addr,
            old_value,
            new_value: counter.value,
        });
    }

    #[view]
    public fun get_value(account_addr: address): u64 acquires Counter {
        borrow_global<Counter>(account_addr).value
    }

    public entry fun set_value(account: &signer, new_value: u64) acquires Counter {
        let account_addr = signer::address_of(account);
        let counter = borrow_global_mut<Counter>(account_addr);
        counter.value = new_value;
    }
}
```

### Move.toml Configuration

```toml
[package]
name = "counter"
version = "1.0.0"
authors = ["your-email@example.com"]

[dependencies.AptosFramework]
git = "https://github.com/aptos-labs/aptos-core.git"
rev = "mainnet"
subdir = "aptos-move/framework/aptos-framework"

[addresses]
my_address = "_"
```

## TypeScript Integration Patterns

### Contract Interaction Utilities

```typescript
// utils/counterContract.ts
import { 
  Aptos, 
  AptosConfig, 
  Network, 
  InputViewFunctionData,
  Account,
  AccountAddress,
} from '@aptos-labs/ts-sdk';

export class CounterContract {
  private moduleAddress: string;
  private moduleName: string = 'counter';

  constructor(
    private aptos: Aptos,
    moduleAddress: string
  ) {
    this.moduleAddress = moduleAddress;
  }

  // Initialize counter for an account
  async initializeCounter(account: Account) {
    const transaction = await this.aptos.transaction.build.simple({
      sender: account.accountAddress,
      data: {
        function: `${this.moduleAddress}::${this.moduleName}::initialize_counter`,
        functionArguments: [],
      },
    });

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

    return this.aptos.waitForTransaction({
      transactionHash: response.hash,
    });
  }

  // Increment counter
  async incrementCounter(account: Account) {
    const transaction = await this.aptos.transaction.build.simple({
      sender: account.accountAddress,
      data: {
        function: `${this.moduleAddress}::${this.moduleName}::increment`,
        functionArguments: [],
      },
    });

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

    return this.aptos.waitForTransaction({
      transactionHash: response.hash,
    });
  }

  // Set counter value
  async setCounterValue(account: Account, value: number) {
    const transaction = await this.aptos.transaction.build.simple({
      sender: account.accountAddress,
      data: {
        function: `${this.moduleAddress}::${this.moduleName}::set_value`,
        functionArguments: [value],
      },
    });

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

    return this.aptos.waitForTransaction({
      transactionHash: response.hash,
    });
  }

  // Read counter value using view function
  async getCounterValue(accountAddress: string): Promise<number> {
    try {
      const result = await this.aptos.view({
        payload: {
          function: `${this.moduleAddress}::${this.moduleName}::get_value`,
          functionArguments: [accountAddress],
        },
      });

      return Number(result[0]);
    } catch (error) {
      console.error('Error reading counter value:', error);
      throw error;
    }
  }

  // Check if counter exists for account
  async hasCounter(accountAddress: string): Promise<boolean> {
    try {
      const resource = await this.aptos.getAccountResource({
        accountAddress,
        resourceType: `${this.moduleAddress}::${this.moduleName}::Counter`,
      });
      return !!resource;
    } catch (error) {
      return false;
    }
  }

  // Get counter resource directly
  async getCounterResource(accountAddress: string) {
    return this.aptos.getAccountResource({
      accountAddress,
      resourceType: `${this.moduleAddress}::${this.moduleName}::Counter`,
    });
  }
}
```

### React Hook for Contract Interaction

```typescript
// hooks/useCounter.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useWallet } from '@aptos-labs/wallet-adapter-react';
import { Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk';
import { CounterContract } from '../utils/counterContract';

const MODULE_ADDRESS = '0x...'; // Your deployed module address

export function useCounter(accountAddress?: string) {
  const { account, signAndSubmitTransaction } = useWallet();
  const queryClient = useQueryClient();
  
  const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));
  const contract = new CounterContract(aptos, MODULE_ADDRESS);

  // Query if counter exists
  const {
    data: hasCounter,
    isLoading: checkingCounter
  } = useQuery({
    queryKey: ['hasCounter', accountAddress],
    queryFn: () => contract.hasCounter(accountAddress!),
    enabled: !!accountAddress,
  });

  // Query counter value
  const {
    data: counterValue,
    isLoading: loadingValue,
    error
  } = useQuery({
    queryKey: ['counterValue', accountAddress],
    queryFn: () => contract.getCounterValue(accountAddress!),
    enabled: !!accountAddress && hasCounter,
    refetchInterval: 5000, // Refresh every 5 seconds
  });

  // Initialize counter mutation
  const initializeCounter = useMutation({
    mutationFn: async () => {
      if (!account) throw new Error('Wallet not connected');

      const transaction = await aptos.transaction.build.simple({
        sender: account.address,
        data: {
          function: `${MODULE_ADDRESS}::counter::initialize_counter`,
          functionArguments: [],
        },
      });

      const response = await signAndSubmitTransaction({ transaction });
      return aptos.waitForTransaction({ transactionHash: response.hash });
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['hasCounter', account?.address] });
      queryClient.invalidateQueries({ queryKey: ['counterValue', account?.address] });
    },
  });

  // Increment mutation
  const incrementCounter = useMutation({
    mutationFn: async () => {
      if (!account) throw new Error('Wallet not connected');

      const transaction = await aptos.transaction.build.simple({
        sender: account.address,
        data: {
          function: `${MODULE_ADDRESS}::counter::increment`,
          functionArguments: [],
        },
      });

      const response = await signAndSubmitTransaction({ transaction });
      return aptos.waitForTransaction({ transactionHash: response.hash });
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['counterValue', account?.address] });
    },
  });

  // Set value mutation
  const setCounterValue = useMutation({
    mutationFn: async (value: number) => {
      if (!account) throw new Error('Wallet not connected');

      const transaction = await aptos.transaction.build.simple({
        sender: account.address,
        data: {
          function: `${MODULE_ADDRESS}::counter::set_value`,
          functionArguments: [value],
        },
      });

      const response = await signAndSubmitTransaction({ transaction });
      return aptos.waitForTransaction({ transactionHash: response.hash });
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['counterValue', account?.address] });
    },
  });

  return {
    hasCounter,
    checkingCounter,
    counterValue,
    loadingValue,
    error,
    initializeCounter,
    incrementCounter,
    setCounterValue,
  };
}
```

### Counter Component Implementation

```typescript
// components/Counter.tsx
import React, { useState } from 'react';
import { useWallet } from '@aptos-labs/wallet-adapter-react';
import { useCounter } from '../hooks/useCounter';

export function Counter() {
  const { account } = useWallet();
  const [newValue, setNewValue] = useState<number>(0);
  
  const {
    hasCounter,
    checkingCounter,
    counterValue,
    loadingValue,
    error,
    initializeCounter,
    incrementCounter,
    setCounterValue,
  } = useCounter(account?.address);

  if (!account) {
    return <div>Please connect your wallet</div>;
  }

  if (checkingCounter) {
    return <div>Checking if counter exists...</div>;
  }

  if (!hasCounter) {
    return (
      <div className="counter-container">
        <h3>Initialize Your Counter</h3>
        <button
          onClick={() => initializeCounter.mutate()}
          disabled={initializeCounter.isPending}
        >
          {initializeCounter.isPending ? 'Initializing...' : 'Initialize Counter'}
        </button>
        {initializeCounter.error && (
          <div className="error">
            Error: {initializeCounter.error.message}
          </div>
        )}
      </div>
    );
  }

  if (loadingValue) return <div>Loading counter value...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div className="counter-container">
      <h3>Your Counter</h3>
      <div className="counter-display">
        <span className="counter-value">{counterValue}</span>
      </div>
      
      <div className="counter-actions">
        <button
          onClick={() => incrementCounter.mutate()}
          disabled={incrementCounter.isPending}
        >
          {incrementCounter.isPending ? 'Incrementing...' : '+1'}
        </button>
        
        <div className="set-value-section">
          <input
            type="number"
            value={newValue}
            onChange={(e) => setNewValue(Number(e.target.value))}
            placeholder="New value"
          />
          <button
            onClick={() => setCounterValue.mutate(newValue)}
            disabled={setCounterValue.isPending}
          >
            {setCounterValue.isPending ? 'Setting...' : 'Set Value'}
          </button>
        </div>
      </div>

      {(incrementCounter.error || setCounterValue.error) && (
        <div className="error">
          Error: {(incrementCounter.error || setCounterValue.error)?.message}
        </div>
      )}
    </div>
  );
}
```

## Advanced Patterns

### Working with Complex Resources

```move
// sources/marketplace.move
module my_address::marketplace {
    use std::signer;
    use std::string::String;
    use aptos_framework::coin;
    use aptos_framework::aptos_coin::AptosCoin;

    struct Item has key, store {
        name: String,
        description: String,
        price: u64,
        owner: address,
        for_sale: bool,
    }

    struct Marketplace has key {
        items: vector<Item>,
        fee_percentage: u8,
    }

    public entry fun create_marketplace(account: &signer, fee_percentage: u8) {
        let marketplace = Marketplace {
            items: vector::empty(),
            fee_percentage,
        };
        move_to(account, marketplace);
    }

    public entry fun list_item(
        seller: &signer,
        marketplace_addr: address,
        name: String,
        description: String,
        price: u64,
    ) acquires Marketplace {
        let seller_addr = signer::address_of(seller);
        let marketplace = borrow_global_mut<Marketplace>(marketplace_addr);
        
        let item = Item {
            name,
            description,
            price,
            owner: seller_addr,
            for_sale: true,
        };
        
        vector::push_back(&mut marketplace.items, item);
    }

    public entry fun buy_item(
        buyer: &signer,
        marketplace_addr: address,
        item_index: u64,
        payment: coin::Coin<AptosCoin>,
    ) acquires Marketplace {
        let marketplace = borrow_global_mut<Marketplace>(marketplace_addr);
        let item = vector::borrow_mut(&mut marketplace.items, item_index);
        
        assert!(item.for_sale, 1);
        assert!(coin::value(&payment) >= item.price, 2);
        
        // Transfer payment to seller (minus fee)
        let fee = (item.price * (marketplace.fee_percentage as u64)) / 100;
        let seller_amount = item.price - fee;
        
        // In a real implementation, you'd split the payment appropriately
        coin::deposit(item.owner, payment);
        
        item.owner = signer::address_of(buyer);
        item.for_sale = false;
    }
}
```

```typescript
// TypeScript integration for marketplace
export class MarketplaceContract {
  constructor(
    private aptos: Aptos,
    private moduleAddress: string
  ) {}

  async createMarketplace(account: Account, feePercentage: number) {
    const transaction = await this.aptos.transaction.build.simple({
      sender: account.accountAddress,
      data: {
        function: `${this.moduleAddress}::marketplace::create_marketplace`,
        functionArguments: [feePercentage],
      },
    });

    return this.aptos.signAndSubmitTransaction({
      signer: account,
      transaction,
    });
  }

  async listItem(
    seller: Account,
    marketplaceAddress: string,
    name: string,
    description: string,
    priceInApt: number
  ) {
    const priceInOctas = priceInApt * 100000000; // Convert APT to Octas

    const transaction = await this.aptos.transaction.build.simple({
      sender: seller.accountAddress,
      data: {
        function: `${this.moduleAddress}::marketplace::list_item`,
        functionArguments: [
          marketplaceAddress,
          name,
          description,
          priceInOctas,
        ],
      },
    });

    return this.aptos.signAndSubmitTransaction({
      signer: seller,
      transaction,
    });
  }

  async getMarketplaceItems(marketplaceAddress: string) {
    try {
      const resource = await this.aptos.getAccountResource({
        accountAddress: marketplaceAddress,
        resourceType: `${this.moduleAddress}::marketplace::Marketplace`,
      });

      return (resource.data as any).items;
    } catch (error) {
      console.error('Error fetching marketplace items:', error);
      throw error;
    }
  }
}
```

### Event Handling and Processing

```typescript
// utils/eventProcessor.ts
import { Aptos, Event } from '@aptos-labs/ts-sdk';

export class EventProcessor {
  constructor(
    private aptos: Aptos,
    private moduleAddress: string
  ) {}

  async getCounterEvents(accountAddress?: string) {
    const events = await this.aptos.getEvents({
      eventType: `${this.moduleAddress}::counter::CounterCreated`,
      options: {
        limit: 100,
        orderBy: [{ transaction_version: 'desc' }],
        where: accountAddress ? {
          account: { _eq: accountAddress }
        } : undefined,
      },
    });

    return events.map(event => ({
      ...event,
      data: event.data as {
        account: string;
        initial_value: string;
      },
    }));
  }

  async getIncrementEvents(accountAddress?: string) {
    const events = await this.aptos.getEvents({
      eventType: `${this.moduleAddress}::counter::CounterIncremented`,
      options: {
        limit: 100,
        orderBy: [{ transaction_version: 'desc' }],
        where: accountAddress ? {
          account: { _eq: accountAddress }
        } : undefined,
      },
    });

    return events.map(event => ({
      ...event,
      data: event.data as {
        account: string;
        old_value: string;
        new_value: string;
      },
    }));
  }

  async subscribeToEvents(
    eventType: string,
    callback: (event: Event) => void,
    pollInterval: number = 5000
  ) {
    let lastVersion = 0;

    const poll = async () => {
      try {
        const events = await this.aptos.getEvents({
          eventType,
          options: {
            limit: 50,
            orderBy: [{ transaction_version: 'asc' }],
            where: {
              transaction_version: { _gt: lastVersion }
            },
          },
        });

        events.forEach(event => {
          if (Number(event.transaction_version) > lastVersion) {
            lastVersion = Number(event.transaction_version);
            callback(event);
          }
        });
      } catch (error) {
        console.error('Error polling events:', error);
      }
    };

    const intervalId = setInterval(poll, pollInterval);
    
    // Initial poll
    poll();

    return () => clearInterval(intervalId);
  }
}
```

### React Hook for Event Monitoring

```typescript
// hooks/useContractEvents.ts
import { useEffect, useState } from 'react';
import { Event } from '@aptos-labs/ts-sdk';
import { EventProcessor } from '../utils/eventProcessor';

export function useContractEvents(
  moduleAddress: string,
  eventType: string,
  accountAddress?: string
) {
  const [events, setEvents] = useState<Event[]>([]);
  const [isListening, setIsListening] = useState(false);

  useEffect(() => {
    const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));
    const eventProcessor = new EventProcessor(aptos, moduleAddress);

    setIsListening(true);

    const unsubscribe = eventProcessor.subscribeToEvents(
      eventType,
      (event) => {
        setEvents(prev => [event, ...prev.slice(0, 99)]); // Keep latest 100 events
      }
    );

    // Load initial events
    const loadInitialEvents = async () => {
      try {
        if (eventType.includes('CounterCreated')) {
          const initialEvents = await eventProcessor.getCounterEvents(accountAddress);
          setEvents(initialEvents);
        } else if (eventType.includes('CounterIncremented')) {
          const initialEvents = await eventProcessor.getIncrementEvents(accountAddress);
          setEvents(initialEvents);
        }
      } catch (error) {
        console.error('Error loading initial events:', error);
      }
    };

    loadInitialEvents();

    return () => {
      unsubscribe();
      setIsListening(false);
    };
  }, [moduleAddress, eventType, accountAddress]);

  return { events, isListening };
}
```

## Testing Strategies

### Unit Tests for Contract Utilities

```typescript
// __tests__/counterContract.test.ts
import { describe, it, expect, beforeEach } from '@jest/globals';
import { Aptos, AptosConfig, Network, Account } from '@aptos-labs/ts-sdk';
import { CounterContract } from '../utils/counterContract';

describe('CounterContract', () => {
  let aptos: Aptos;
  let contract: CounterContract;
  let account: Account;

  beforeEach(() => {
    aptos = new Aptos(new AptosConfig({ network: Network.DEVNET }));
    contract = new CounterContract(aptos, '0x123');
    account = Account.generate();
  });

  it('should check if counter exists', async () => {
    const hasCounter = await contract.hasCounter(account.accountAddress.toString());
    expect(typeof hasCounter).toBe('boolean');
  });

  it('should get counter value for existing counter', async () => {
    // Mock the aptos.view method
    jest.spyOn(aptos, 'view').mockResolvedValue([42]);
    
    const value = await contract.getCounterValue(account.accountAddress.toString());
    expect(value).toBe(42);
  });
});
```

### Integration Tests with Local Network

```typescript
// __tests__/integration.test.ts
import { describe, it, expect, beforeAll } from '@jest/globals';
import { Aptos, AptosConfig, Network, Account } from '@aptos-labs/ts-sdk';
import { CounterContract } from '../utils/counterContract';

describe('Counter Integration Tests', () => {
  let aptos: Aptos;
  let account: Account;
  let contract: CounterContract;
  let moduleAddress: string;

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

    // Deploy contract and get module address
    moduleAddress = await deployCounterModule(aptos, account);
    contract = new CounterContract(aptos, moduleAddress);
  });

  it('should initialize and increment counter', async () => {
    // Check counter doesn't exist initially
    const initialExists = await contract.hasCounter(account.accountAddress.toString());
    expect(initialExists).toBe(false);

    // Initialize counter
    const initResult = await contract.initializeCounter(account);
    expect(initResult.success).toBe(true);

    // Check counter now exists
    const existsAfterInit = await contract.hasCounter(account.accountAddress.toString());
    expect(existsAfterInit).toBe(true);

    // Check initial value is 0
    const initialValue = await contract.getCounterValue(account.accountAddress.toString());
    expect(initialValue).toBe(0);

    // Increment counter
    const incrementResult = await contract.incrementCounter(account);
    expect(incrementResult.success).toBe(true);

    // Check value is now 1
    const newValue = await contract.getCounterValue(account.accountAddress.toString());
    expect(newValue).toBe(1);
  });

  it('should set counter to specific value', async () => {
    const setValue = 42;
    const setResult = await contract.setCounterValue(account, setValue);
    expect(setResult.success).toBe(true);

    const value = await contract.getCounterValue(account.accountAddress.toString());
    expect(value).toBe(setValue);
  });
});

async function deployCounterModule(aptos: Aptos, account: Account): Promise<string> {
  // Implementation would involve:
  // 1. Compiling Move code
  // 2. Publishing the module
  // 3. Returning the module address
  
  // For testing, return a mock address
  return account.accountAddress.toString();
}
```

### Component Testing with React Testing Library

```typescript
// __tests__/Counter.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { WalletProvider } from '@aptos-labs/wallet-adapter-react';
import { Counter } from '../components/Counter';
import { useCounter } from '../hooks/useCounter';

// Mock the useCounter hook
jest.mock('../hooks/useCounter');
const mockUseCounter = useCounter as jest.MockedFunction<typeof useCounter>;

// Mock wallet context
jest.mock('@aptos-labs/wallet-adapter-react', () => ({
  ...jest.requireActual('@aptos-labs/wallet-adapter-react'),
  useWallet: () => ({
    account: { address: '0x123' },
    connected: true,
  }),
}));

describe('Counter Component', () => {
  let queryClient: QueryClient;

  beforeEach(() => {
    queryClient = new QueryClient({
      defaultOptions: {
        queries: { retry: false },
        mutations: { retry: false },
      },
    });
  });

  it('should display initialize button when counter does not exist', async () => {
    mockUseCounter.mockReturnValue({
      hasCounter: false,
      checkingCounter: false,
      counterValue: undefined,
      loadingValue: false,
      error: null,
      initializeCounter: { mutate: jest.fn(), isPending: false, error: null },
      incrementCounter: { mutate: jest.fn(), isPending: false, error: null },
      setCounterValue: { mutate: jest.fn(), isPending: false, error: null },
    });

    render(
      <QueryClientProvider client={queryClient}>
        <Counter />
      </QueryClientProvider>
    );

    expect(screen.getByText('Initialize Counter')).toBeInTheDocument();
  });

  it('should display counter value when counter exists', async () => {
    mockUseCounter.mockReturnValue({
      hasCounter: true,
      checkingCounter: false,
      counterValue: 42,
      loadingValue: false,
      error: null,
      initializeCounter: { mutate: jest.fn(), isPending: false, error: null },
      incrementCounter: { mutate: jest.fn(), isPending: false, error: null },
      setCounterValue: { mutate: jest.fn(), isPending: false, error: null },
    });

    render(
      <QueryClientProvider client={queryClient}>
        <Counter />
      </QueryClientProvider>
    );

    await waitFor(() => {
      expect(screen.getByText('42')).toBeInTheDocument();
    });
  });

  it('should call increment when +1 button is clicked', async () => {
    const mockIncrement = jest.fn();
    mockUseCounter.mockReturnValue({
      hasCounter: true,
      checkingCounter: false,
      counterValue: 0,
      loadingValue: false,
      error: null,
      initializeCounter: { mutate: jest.fn(), isPending: false, error: null },
      incrementCounter: { mutate: mockIncrement, isPending: false, error: null },
      setCounterValue: { mutate: jest.fn(), isPending: false, error: null },
    });

    render(
      <QueryClientProvider client={queryClient}>
        <Counter />
      </QueryClientProvider>
    );

    fireEvent.click(screen.getByText('+1'));
    expect(mockIncrement).toHaveBeenCalled();
  });
});
```

## Deployment and Production Considerations

### Module Publishing Script

```typescript
// scripts/deploy.ts
import { Aptos, AptosConfig, Network, Account } from '@aptos-labs/ts-sdk';
import * as fs from 'fs';
import * as path from 'path';

async function deployModule() {
  const network = process.env.APTOS_NETWORK as Network || Network.DEVNET;
  const aptos = new Aptos(new AptosConfig({ network }));
  
  // Load deployer account from environment
  const privateKey = process.env.DEPLOYER_PRIVATE_KEY;
  if (!privateKey) {
    throw new Error('DEPLOYER_PRIVATE_KEY environment variable not set');
  }
  
  const account = Account.fromPrivateKey({ privateKey });
  
  // Read compiled module bytecode
  const packagePath = path.join(__dirname, '../move/build/counter');
  const packageMetadata = JSON.parse(
    fs.readFileSync(path.join(packagePath, 'package-metadata.json'), 'utf8')
  );
  
  const modules = packageMetadata.modules.map((module: any) => ({
    bytecode: module.bytecode,
  }));

  // Publish the module
  const transaction = await aptos.transaction.build.simple({
    sender: account.accountAddress,
    data: {
      function: '0x1::code::publish_package_txn',
      functionArguments: [
        packageMetadata.source_digest,
        modules,
      ],
    },
  });

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

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

  console.log('Module deployed successfully!');
  console.log('Transaction hash:', response.hash);
  console.log('Module address:', account.accountAddress.toString());
  
  return {
    moduleAddress: account.accountAddress.toString(),
    transactionHash: response.hash,
  };
}

if (require.main === module) {
  deployModule().catch(console.error);
}
```

### Environment Configuration

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

export const getAptosConfig = () => {
  const environment = process.env.NODE_ENV;
  const network = process.env.NEXT_PUBLIC_APTOS_NETWORK as Network;
  
  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 || Network.DEVNET,
      });
  }
};

export const getModuleAddress = () => {
  const environment = process.env.NODE_ENV;
  
  switch (environment) {
    case 'production':
      return process.env.NEXT_PUBLIC_COUNTER_MODULE_MAINNET;
    case 'staging':
      return process.env.NEXT_PUBLIC_COUNTER_MODULE_TESTNET;
    default:
      return process.env.NEXT_PUBLIC_COUNTER_MODULE_DEVNET;
  }
};
```

## Best Practices Summary

1. **Use TypeScript** for better type safety with Move contracts
2. **Implement proper error handling** for all contract interactions
3. **Use view functions** for reading data efficiently
4. **Validate inputs** before sending transactions
5. **Handle resource states** (existence checks) properly
6. **Monitor events** for real-time updates
7. **Test thoroughly** with both unit and integration tests
8. **Use React Query** for efficient data fetching and caching
9. **Secure deployment** using environment variables
10. **Version your contracts** for smooth upgrades

## Next Steps

- Explore [Multi-Chain dApp Architecture](../multi-chain-architecture.md) for cross-chain applications
- Implement performance optimizations for production applications
- Follow security best practices for production deployment

## Resources

- [Aptos Move Documentation](https://aptos.dev/build/smart-contracts)
- [Aptos TypeScript SDK](https://aptos-labs.github.io/aptos-ts-sdk/)
- [Move Book](https://move-language.github.io/move/)
- bex.co Aptos API