---
title: "Sui dApp Development Guide"
sidebar_position: 4
tags: [SUI, dApp, development, frontend, TypeScript]
keywords: [Sui dApp, blockchain development, TypeScript SDK, wallet integration, transaction building]
description: "Complete guide to building modern decentralized applications on Sui blockchain using the latest @mysten/sui SDK and @mysten/dapp-kit."
last_updated: 2026-08-20
---

# Sui dApp Development Guide

This comprehensive guide covers building modern decentralized applications (dApps) on the Sui blockchain using the latest tools and 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)
- **Sui wallet installed** (Sui Wallet, Suiet, or other Sui-compatible wallets)

## Core Technologies

### Essential Packages

```bash
# Core Sui SDK (modular imports)
npm install @mysten/sui

# dApp Kit for React applications
npm install @mysten/dapp-kit

# Additional utilities
npm install @mysten/dapp-kit @tanstack/react-query
```

### Package Overview

- **@mysten/sui**: Core SDK for blockchain interactions
- **@mysten/dapp-kit**: React hooks and components for wallet integration
- **@tanstack/react-query**: State management for async data (recommended)

## Basic Setup

### 1. Initialize Sui Client

```typescript
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';

// Initialize client for different networks
const client = new SuiClient({ 
  url: getFullnodeUrl('mainnet') // 'testnet', 'devnet', 'localnet'
});

// Using bex.co endpoint
const bexClient = new SuiClient({
  url: 'https://api.bex.co/sui/<access_key>'
});
```

### 2. React dApp Setup with dApp Kit

```typescript
// App.tsx
import { SuiClientProvider, WalletProvider } from '@mysten/dapp-kit';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { getFullnodeUrl } from '@mysten/sui/client';

const queryClient = new QueryClient();
const networks = {
  mainnet: { url: getFullnodeUrl('mainnet') },
  testnet: { url: getFullnodeUrl('testnet') },
};

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <SuiClientProvider networks={networks} defaultNetwork="testnet">
        <WalletProvider>
          <MyDApp />
        </WalletProvider>
      </SuiClientProvider>
    </QueryClientProvider>
  );
}
```

### 3. Wallet Connection Component

```typescript
// WalletConnection.tsx
import { ConnectButton, useCurrentAccount } from '@mysten/dapp-kit';

export function WalletConnection() {
  const currentAccount = useCurrentAccount();

  return (
    <div className="wallet-section">
      <ConnectButton />
      {currentAccount && (
        <div>
          <p>Connected: {currentAccount.address}</p>
        </div>
      )}
    </div>
  );
}
```

## Working with Transactions

### Building and Executing Transactions

```typescript
import { Transaction } from '@mysten/sui/transactions';
import { useSignAndExecuteTransaction, useSuiClient } from '@mysten/dapp-kit';

export function TransferSui() {
  const client = useSuiClient();
  const { mutate: signAndExecute } = useSignAndExecuteTransaction();

  const transferSui = async (recipient: string, amount: number) => {
    const tx = new Transaction();
    
    // Convert SUI to MIST (1 SUI = 1,000,000,000 MIST)
    const amountMIST = amount * 1_000_000_000;
    
    // Split coins and transfer
    const [coin] = tx.splitCoins(tx.gas, [amountMIST]);
    tx.transferObjects([coin], recipient);
    
    // Execute transaction
    signAndExecute(
      { transaction: tx },
      {
        onSuccess: (result) => {
          console.log('Transfer successful:', result.digest);
        },
        onError: (error) => {
          console.error('Transfer failed:', error);
        },
      }
    );
  };

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

### Move Call Transactions

```typescript
import { Transaction } from '@mysten/sui/transactions';

export function MoveCallExample() {
  const { mutate: signAndExecute } = useSignAndExecuteTransaction();

  const callMoveFunction = async () => {
    const tx = new Transaction();
    
    // Call a Move function
    tx.moveCall({
      target: '0x2::coin::split',
      arguments: [
        tx.object('0x...'), // coin object
        tx.pure.u64(1000000000), // amount in MIST
      ],
    });
    
    signAndExecute({ transaction: tx });
  };

  return <button onClick={callMoveFunction}>Call Move Function</button>;
}
```

## Data Fetching Patterns

### Using React Query with Sui Data

```typescript
import { useQuery } from '@tanstack/react-query';
import { useSuiClient } from '@mysten/dapp-kit';

export function AccountBalance({ address }: { address: string }) {
  const client = useSuiClient();

  const { data: balance, isLoading, error } = useQuery({
    queryKey: ['balance', address],
    queryFn: () => client.getBalance({ owner: address }),
    enabled: !!address,
    refetchInterval: 10000, // Refetch every 10 seconds
  });

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

  return (
    <div>
      Balance: {balance ? Number(balance.totalBalance) / 1_000_000_000 : 0} SUI
    </div>
  );
}
```

### Fetching Objects and Events

```typescript
export function ObjectsDisplay({ address }: { address: string }) {
  const client = useSuiClient();

  const { data: objects } = useQuery({
    queryKey: ['objects', address],
    queryFn: () => client.getOwnedObjects({
      owner: address,
      options: {
        showContent: true,
        showType: true,
      },
    }),
    enabled: !!address,
  });

  return (
    <div>
      <h3>Owned Objects</h3>
      {objects?.data.map((obj) => (
        <div key={obj.data?.objectId}>
          <p>Object ID: {obj.data?.objectId}</p>
          <p>Type: {obj.data?.type}</p>
        </div>
      ))}
    </div>
  );
}
```

## Event Handling and Real-time Updates

### Listening to Events

```typescript
import { useEffect, useState } from 'react';
import { useSuiClient } from '@mysten/dapp-kit';

export function EventListener() {
  const client = useSuiClient();
  const [events, setEvents] = useState<any[]>([]);

  useEffect(() => {
    const fetchEvents = async () => {
      try {
        const eventData = await client.queryEvents({
          query: { MoveModule: { package: '0x2', module: 'coin' } },
          limit: 10,
          order: 'descending',
        });
        setEvents(eventData.data);
      } catch (error) {
        console.error('Error fetching events:', error);
      }
    };

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

  return (
    <div>
      <h3>Recent Events</h3>
      {events.map((event, index) => (
        <div key={index}>
          <p>Type: {event.type}</p>
          <p>Timestamp: {event.timestampMs}</p>
        </div>
      ))}
    </div>
  );
}
```

## Error Handling Best Practices

### Comprehensive Error Handling

```typescript
import { SuiError } from '@mysten/sui/client';

export function RobustTransaction() {
  const { mutate: signAndExecute } = useSignAndExecuteTransaction();

  const executeWithErrorHandling = async () => {
    const tx = new Transaction();
    // ... build transaction

    signAndExecute(
      { transaction: tx },
      {
        onSuccess: (result) => {
          if (result.effects?.status?.status === 'success') {
            console.log('Transaction succeeded:', result.digest);
          } else {
            console.error('Transaction failed:', result.effects?.status);
          }
        },
        onError: (error) => {
          if (error instanceof SuiError) {
            // Handle Sui-specific errors
            console.error('Sui error:', error.message);
          } else if (error.message.includes('User rejected')) {
            // Handle user rejection
            console.log('User cancelled transaction');
          } else {
            // Handle other errors
            console.error('Unexpected error:', error);
          }
        },
      }
    );
  };

  return <button onClick={executeWithErrorHandling}>Execute Transaction</button>;
}
```

### Network Error Handling

```typescript
export function useResilientQuery<T>(
  queryKey: any[],
  queryFn: () => Promise<T>,
  options?: any
) {
  return useQuery({
    queryKey,
    queryFn,
    retry: (failureCount, error) => {
      // Retry up to 3 times for network errors
      if (failureCount < 3 && error.message.includes('network')) {
        return true;
      }
      return false;
    },
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
    ...options,
  });
}
```

## Performance Optimization

### Efficient Data Loading

```typescript
// Batch multiple queries for better performance
export function BatchedDataLoader({ addresses }: { addresses: string[] }) {
  const client = useSuiClient();

  const { data: balances } = useQuery({
    queryKey: ['batchedBalances', addresses],
    queryFn: async () => {
      // Use Promise.all for concurrent requests
      const balancePromises = addresses.map(address => 
        client.getBalance({ owner: address })
      );
      return Promise.all(balancePromises);
    },
    enabled: addresses.length > 0,
  });

  return (
    <div>
      {balances?.map((balance, index) => (
        <div key={addresses[index]}>
          Address: {addresses[index]} - 
          Balance: {Number(balance.totalBalance) / 1_000_000_000} SUI
        </div>
      ))}
    </div>
  );
}
```

### Memoization for Heavy Computations

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

export function ProcessedData({ objects }: { objects: any[] }) {
  const processedData = useMemo(() => {
    return objects
      .filter(obj => obj.data?.type?.includes('::coin::'))
      .map(obj => ({
        id: obj.data.objectId,
        balance: obj.data.content?.fields?.balance || 0,
      }))
      .sort((a, b) => b.balance - a.balance);
  }, [objects]);

  return (
    <div>
      {processedData.map(item => (
        <div key={item.id}>
          Coin: {item.id} - Balance: {item.balance}
        </div>
      ))}
    </div>
  );
}
```

## Testing Strategies

### Unit Testing with Jest

```typescript
// __tests__/utils.test.ts
import { describe, it, expect } from '@jest/globals';
import { formatSuiAmount, validateSuiAddress } from '../utils';

describe('Sui utilities', () => {
  it('should format SUI amounts correctly', () => {
    expect(formatSuiAmount(1000000000)).toBe('1.00');
    expect(formatSuiAmount(1500000000)).toBe('1.50');
  });

  it('should validate Sui addresses', () => {
    expect(validateSuiAddress('0x1')).toBe(true);
    expect(validateSuiAddress('invalid')).toBe(false);
  });
});
```

### Integration Testing

```typescript
// __tests__/integration.test.ts
import { render, screen, waitFor } from '@testing-library/react';
import { QueryClient } from '@tanstack/react-query';
import { SuiClient } from '@mysten/sui/client';
import { WalletConnection } from '../components/WalletConnection';

describe('Wallet Integration', () => {
  it('should display connection status', async () => {
    render(<WalletConnection />);
    
    await waitFor(() => {
      expect(screen.getByText('Connect Wallet')).toBeInTheDocument();
    });
  });
});
```

## Deployment Considerations

### Environment Configuration

```typescript
// config/sui.ts
export const getSuiConfig = () => {
  const environment = process.env.NODE_ENV;
  
  switch (environment) {
    case 'production':
      return {
        network: 'mainnet',
        rpcUrl: process.env.NEXT_PUBLIC_SUI_MAINNET_URL,
      };
    case 'staging':
      return {
        network: 'testnet',
        rpcUrl: process.env.NEXT_PUBLIC_SUI_TESTNET_URL,
      };
    default:
      return {
        network: 'devnet',
        rpcUrl: getFullnodeUrl('devnet'),
      };
  }
};
```

### Production Optimizations

```typescript
// Use environment variables for API keys
const client = new SuiClient({
  url: process.env.NEXT_PUBLIC_BEX_SUI_URL || getFullnodeUrl('mainnet')
});

// Implement proper error boundaries
export class SuiErrorBoundary extends React.Component {
  constructor(props: any) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: any) {
    console.error('Sui dApp error:', error, errorInfo);
    // Log to error reporting service
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong with the Sui connection.</h1>;
    }

    return this.props.children;
  }
}
```

## Best Practices Summary

1. **Use TypeScript** for better type safety and developer experience
2. **Implement proper error handling** for all blockchain interactions
3. **Cache data effectively** using React Query or similar libraries
4. **Test thoroughly** with both unit and integration tests
5. **Monitor performance** and optimize data fetching patterns
6. **Handle network failures** gracefully with retry logic
7. **Secure API keys** using environment variables
8. **Use modular imports** from @mysten/sui for smaller bundle sizes

## Next Steps

- Explore the [Sui Move Integration Guide](./sui-move-integration.md) for smart contract interactions
- Implement performance optimization techniques for production
- Check out the [Multi-Chain dApp Architecture](../multi-chain-architecture.md) for cross-chain applications

## Resources

- [Sui TypeScript SDK Documentation](https://sdk.mystenlabs.com/)
- [Sui dApp Kit Documentation](https://sdk.mystenlabs.com/dapp-kit)
- [Sui Developer Documentation](https://docs.sui.io/)
- [bex.co Sui API](https://bex.co/api-marketplace/sui)