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
# 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
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
// Initialize client for different networks
const client = new SuiClient({ 
  url: getFullnodeUrl('mainnet') // 'testnet', 'devnet', 'localnet'
});
// Using BlockEden.xyz endpoint
const blockEdenClient = new SuiClient({
  url: 'https://api.blockeden.xyz/sui/<access_key>'
});
2. React dApp Setup with dApp Kit
// 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
// 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
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
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
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
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
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
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
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
// 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
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
// __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
// __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
// 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
// Use environment variables for API keys
const client = new SuiClient({
  url: process.env.NEXT_PUBLIC_BLOCKEDEN_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
- Use TypeScript for better type safety and developer experience
- Implement proper error handling for all blockchain interactions
- Cache data effectively using React Query or similar libraries
- Test thoroughly with both unit and integration tests
- Monitor performance and optimize data fetching patterns
- Handle network failures gracefully with retry logic
- Secure API keys using environment variables
- Use modular imports from @mysten/sui for smaller bundle sizes
Next Steps
- Explore the Sui Move Integration Guide for smart contract interactions
- Implement performance optimization techniques for production
- Check out the Multi-Chain dApp Architecture for cross-chain applications