---
title: "Sui Move Integration Guide"
sidebar_position: 5
tags: [SUI, Move, smart contracts, TypeScript, integration]
keywords: [Sui Move, smart contracts, TypeScript integration, PTB, events, testing]
description: "Complete guide to integrating Sui Move smart contracts with TypeScript applications using modern SDK patterns and best practices."
last_updated: 2026-08-20
---

# Sui Move Integration Guide

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

## Prerequisites

- **Sui CLI installed** for Move development
- **Understanding of Move language** basics
- **TypeScript/React knowledge** for frontend integration
- **@mysten/sui SDK** for blockchain interactions

## Move Contract Structure

### Basic Move Module Example

```move
// sources/counter.move
module my_package::counter {
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    public struct Counter has key, store {
        id: UID,
        value: u64,
    }

    public struct CounterCreated has copy, drop {
        counter_id: ID,
        initial_value: u64,
    }

    public fun create_counter(ctx: &mut TxContext): Counter {
        let counter = Counter {
            id: object::new(ctx),
            value: 0,
        };
        
        sui::event::emit(CounterCreated {
            counter_id: object::id(&counter),
            initial_value: 0,
        });
        
        counter
    }

    public entry fun increment(counter: &mut Counter) {
        counter.value = counter.value + 1;
    }

    public fun get_value(counter: &Counter): u64 {
        counter.value
    }

    public entry fun set_value(counter: &mut Counter, new_value: u64) {
        counter.value = new_value;
    }
}
```

### Move.toml Configuration

```toml
[package]
name = "my_package"
version = "1.0.0"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/mainnet" }

[addresses]
my_package = "0x0"
```

## TypeScript Integration Patterns

### Contract Interaction Utilities

```typescript
// utils/contract.ts
import { Transaction } from '@mysten/sui/transactions';
import { SuiClient } from '@mysten/sui/client';

export class CounterContract {
  constructor(
    private client: SuiClient,
    private packageId: string
  ) {}

  // Create a new counter
  createCounter(): Transaction {
    const tx = new Transaction();
    
    const [counter] = tx.moveCall({
      target: `${this.packageId}::counter::create_counter`,
    });
    
    // Transfer the counter to the user
    tx.transferObjects([counter], tx.pure.address('{{user_address}}'));
    
    return tx;
  }

  // Increment counter value
  incrementCounter(counterId: string): Transaction {
    const tx = new Transaction();
    
    tx.moveCall({
      target: `${this.packageId}::counter::increment`,
      arguments: [tx.object(counterId)],
    });
    
    return tx;
  }

  // Set counter to specific value
  setCounterValue(counterId: string, value: number): Transaction {
    const tx = new Transaction();
    
    tx.moveCall({
      target: `${this.packageId}::counter::set_value`,
      arguments: [
        tx.object(counterId),
        tx.pure.u64(value),
      ],
    });
    
    return tx;
  }

  // Read counter value (view function)
  async getCounterValue(counterId: string): Promise<number> {
    try {
      const result = await this.client.getObject({
        id: counterId,
        options: { showContent: true },
      });

      if (result.data?.content?.dataType === 'moveObject') {
        const fields = result.data.content.fields as any;
        return Number(fields.value);
      }
      
      throw new Error('Counter object not found or invalid');
    } catch (error) {
      console.error('Error reading counter value:', error);
      throw error;
    }
  }
}
```

### React Hook for Contract Interaction

```typescript
// hooks/useCounter.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useSignAndExecuteTransaction, useSuiClient, useCurrentAccount } from '@mysten/dapp-kit';
import { CounterContract } from '../utils/contract';

const PACKAGE_ID = '0x...'; // Your deployed package ID

export function useCounter(counterId?: string) {
  const client = useSuiClient();
  const account = useCurrentAccount();
  const queryClient = useQueryClient();
  const { mutate: signAndExecute } = useSignAndExecuteTransaction();

  const contract = new CounterContract(client, PACKAGE_ID);

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

  // Create counter mutation
  const createCounter = useMutation({
    mutationFn: async () => {
      if (!account) throw new Error('Wallet not connected');
      
      const tx = contract.createCounter();
      
      return new Promise((resolve, reject) => {
        signAndExecute(
          { 
            transaction: tx.replaceTemplateValue('{{user_address}}', account.address) 
          },
          {
            onSuccess: (result) => {
              queryClient.invalidateQueries({ queryKey: ['counters'] });
              resolve(result);
            },
            onError: reject,
          }
        );
      });
    },
  });

  // Increment mutation
  const incrementCounter = useMutation({
    mutationFn: async () => {
      if (!counterId) throw new Error('No counter ID provided');
      
      const tx = contract.incrementCounter(counterId);
      
      return new Promise((resolve, reject) => {
        signAndExecute(
          { transaction: tx },
          {
            onSuccess: (result) => {
              queryClient.invalidateQueries({ queryKey: ['counter', counterId] });
              resolve(result);
            },
            onError: reject,
          }
        );
      });
    },
  });

  // Set value mutation
  const setCounterValue = useMutation({
    mutationFn: async (value: number) => {
      if (!counterId) throw new Error('No counter ID provided');
      
      const tx = contract.setCounterValue(counterId, value);
      
      return new Promise((resolve, reject) => {
        signAndExecute(
          { transaction: tx },
          {
            onSuccess: (result) => {
              queryClient.invalidateQueries({ queryKey: ['counter', counterId] });
              resolve(result);
            },
            onError: reject,
          }
        );
      });
    },
  });

  return {
    counterValue,
    isLoading,
    error,
    createCounter,
    incrementCounter,
    setCounterValue,
  };
}
```

### Counter Component Implementation

```typescript
// components/Counter.tsx
import React, { useState } from 'react';
import { useCounter } from '../hooks/useCounter';

interface CounterProps {
  counterId?: string;
}

export function Counter({ counterId }: CounterProps) {
  const [newValue, setNewValue] = useState<number>(0);
  const {
    counterValue,
    isLoading,
    error,
    createCounter,
    incrementCounter,
    setCounterValue,
  } = useCounter(counterId);

  if (!counterId) {
    return (
      <div className="counter-container">
        <h3>Create a Counter</h3>
        <button
          onClick={() => createCounter.mutate()}
          disabled={createCounter.isPending}
        >
          {createCounter.isPending ? 'Creating...' : 'Create Counter'}
        </button>
      </div>
    );
  }

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

  return (
    <div className="counter-container">
      <h3>Counter: {counterId.slice(0, 8)}...</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>
    </div>
  );
}
```

## Advanced Patterns

### Complex Move Calls with Multiple Arguments

```move
// sources/marketplace.move
module my_package::marketplace {
    use sui::coin::{Self, Coin};
    use sui::sui::SUI;
    use sui::transfer;

    public struct Listing has key, store {
        id: UID,
        item: ID,
        price: u64,
        seller: address,
    }

    public entry fun create_listing(
        item_id: ID,
        price: u64,
        ctx: &mut TxContext
    ) {
        let listing = Listing {
            id: object::new(ctx),
            item: item_id,
            price,
            seller: tx_context::sender(ctx),
        };
        
        transfer::share_object(listing);
    }

    public entry fun purchase_item(
        listing: &mut Listing,
        payment: Coin<SUI>,
        ctx: &mut TxContext
    ) {
        assert!(coin::value(&payment) >= listing.price, 0);
        
        // Transfer payment to seller
        transfer::public_transfer(payment, listing.seller);
        
        // Additional marketplace logic...
    }
}
```

```typescript
// TypeScript integration for marketplace
export class MarketplaceContract {
  constructor(private client: SuiClient, private packageId: string) {}

  createListing(itemId: string, priceInSui: number): Transaction {
    const tx = new Transaction();
    
    tx.moveCall({
      target: `${this.packageId}::marketplace::create_listing`,
      arguments: [
        tx.pure.id(itemId),
        tx.pure.u64(priceInSui * 1_000_000_000), // Convert SUI to MIST
      ],
    });
    
    return tx;
  }

  purchaseItem(listingId: string, priceInSui: number): Transaction {
    const tx = new Transaction();
    
    // Split SUI for payment
    const [payment] = tx.splitCoins(tx.gas, [priceInSui * 1_000_000_000]);
    
    tx.moveCall({
      target: `${this.packageId}::marketplace::purchase_item`,
      arguments: [
        tx.object(listingId),
        payment,
      ],
    });
    
    return tx;
  }
}
```

### Event Listening and Processing

```typescript
// utils/eventListener.ts
import { SuiClient } from '@mysten/sui/client';
import { SuiEvent } from '@mysten/sui/client';

export class ContractEventListener {
  constructor(
    private client: SuiClient,
    private packageId: string
  ) {}

  async subscribeToCounterEvents(callback: (event: SuiEvent) => void) {
    try {
      // Query recent events
      const events = await this.client.queryEvents({
        query: {
          MoveModule: {
            package: this.packageId,
            module: 'counter',
          },
        },
        limit: 50,
        order: 'descending',
      });

      // Process existing events
      events.data.forEach(callback);

      // Set up polling for new events
      return setInterval(async () => {
        const newEvents = await this.client.queryEvents({
          query: {
            MoveModule: {
              package: this.packageId,
              module: 'counter',
            },
          },
          limit: 10,
          order: 'descending',
        });

        newEvents.data.forEach(callback);
      }, 5000);
    } catch (error) {
      console.error('Error setting up event subscription:', error);
      throw error;
    }
  }

  async getCounterCreatedEvents(): Promise<SuiEvent[]> {
    const events = await this.client.queryEvents({
      query: {
        MoveEventType: `${this.packageId}::counter::CounterCreated`,
      },
      limit: 100,
      order: 'descending',
    });

    return events.data;
  }
}
```

### React Hook for Event Listening

```typescript
// hooks/useContractEvents.ts
import { useEffect, useState } from 'react';
import { useSuiClient } from '@mysten/dapp-kit';
import { SuiEvent } from '@mysten/sui/client';
import { ContractEventListener } from '../utils/eventListener';

export function useContractEvents(packageId: string) {
  const client = useSuiClient();
  const [events, setEvents] = useState<SuiEvent[]>([]);
  const [isListening, setIsListening] = useState(false);

  useEffect(() => {
    let intervalId: NodeJS.Timeout;
    
    const startListening = async () => {
      setIsListening(true);
      const eventListener = new ContractEventListener(client, packageId);
      
      try {
        intervalId = await eventListener.subscribeToCounterEvents((event) => {
          setEvents(prev => {
            // Avoid duplicates
            const exists = prev.some(e => e.id.txDigest === event.id.txDigest);
            if (!exists) {
              return [event, ...prev].slice(0, 100); // Keep latest 100 events
            }
            return prev;
          });
        });
      } catch (error) {
        console.error('Failed to start event listening:', error);
        setIsListening(false);
      }
    };

    startListening();

    return () => {
      if (intervalId) {
        clearInterval(intervalId);
      }
      setIsListening(false);
    };
  }, [client, packageId]);

  return { events, isListening };
}
```

## Testing Strategies

### Unit Tests for Contract Utilities

```typescript
// __tests__/contract.test.ts
import { describe, it, expect, beforeEach } from '@jest/globals';
import { SuiClient } from '@mysten/sui/client';
import { CounterContract } from '../utils/contract';

describe('CounterContract', () => {
  let client: SuiClient;
  let contract: CounterContract;

  beforeEach(() => {
    client = new SuiClient({ url: 'http://localhost:9000' });
    contract = new CounterContract(client, '0x123');
  });

  it('should create counter transaction', () => {
    const tx = contract.createCounter();
    expect(tx).toBeDefined();
    // Add more specific transaction validation
  });

  it('should create increment transaction', () => {
    const counterId = '0xabc123';
    const tx = contract.incrementCounter(counterId);
    expect(tx).toBeDefined();
  });
});
```

### Integration Tests with Local Network

```typescript
// __tests__/integration.test.ts
import { describe, it, expect, beforeAll } from '@jest/globals';
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { CounterContract } from '../utils/contract';

describe('Counter Integration Tests', () => {
  let client: SuiClient;
  let keypair: Ed25519Keypair;
  let contract: CounterContract;
  let packageId: string;

  beforeAll(async () => {
    // Setup local network client
    client = new SuiClient({ url: getFullnodeUrl('localnet') });
    keypair = new Ed25519Keypair();
    
    // Deploy contract and get package ID
    packageId = await deployContract();
    contract = new CounterContract(client, packageId);
  });

  it('should create and increment counter', async () => {
    // Create counter
    const createTx = contract.createCounter();
    const createResult = await client.signAndExecuteTransaction({
      transaction: createTx,
      signer: keypair,
    });

    expect(createResult.effects?.status?.status).toBe('success');

    // Extract counter ID from created objects
    const counterId = createResult.effects?.created?.[0]?.reference?.objectId;
    expect(counterId).toBeDefined();

    // Increment counter
    const incrementTx = contract.incrementCounter(counterId!);
    const incrementResult = await client.signAndExecuteTransaction({
      transaction: incrementTx,
      signer: keypair,
    });

    expect(incrementResult.effects?.status?.status).toBe('success');

    // Verify counter value
    const value = await contract.getCounterValue(counterId!);
    expect(value).toBe(1);
  });
});

async function deployContract(): Promise<string> {
  // Implementation for deploying contract in test environment
  // This would involve publishing the Move package
  return '0x...'; // Return actual package ID
}
```

### Mock 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 { 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>;

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

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

  it('should display counter value', async () => {
    mockUseCounter.mockReturnValue({
      counterValue: 42,
      isLoading: false,
      error: null,
      createCounter: { mutate: jest.fn(), isPending: false },
      incrementCounter: { mutate: jest.fn(), isPending: false },
      setCounterValue: { mutate: jest.fn(), isPending: false },
    });

    render(
      <QueryClientProvider client={queryClient}>
        <Counter counterId="0x123" />
      </QueryClientProvider>
    );

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

  it('should call increment when button is clicked', async () => {
    const mockIncrement = jest.fn();
    mockUseCounter.mockReturnValue({
      counterValue: 0,
      isLoading: false,
      error: null,
      createCounter: { mutate: jest.fn(), isPending: false },
      incrementCounter: { mutate: mockIncrement, isPending: false },
      setCounterValue: { mutate: jest.fn(), isPending: false },
    });

    render(
      <QueryClientProvider client={queryClient}>
        <Counter counterId="0x123" />
      </QueryClientProvider>
    );

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

## Deployment and Production Considerations

### Environment-Specific Configuration

```typescript
// config/contracts.ts
export const getContractConfig = () => {
  const environment = process.env.NODE_ENV;
  
  switch (environment) {
    case 'production':
      return {
        packageId: process.env.NEXT_PUBLIC_COUNTER_PACKAGE_MAINNET!,
        network: 'mainnet',
      };
    case 'staging':
      return {
        packageId: process.env.NEXT_PUBLIC_COUNTER_PACKAGE_TESTNET!,
        network: 'testnet',
      };
    default:
      return {
        packageId: process.env.NEXT_PUBLIC_COUNTER_PACKAGE_DEVNET!,
        network: 'devnet',
      };
  }
};
```

### Error Handling and Monitoring

```typescript
// utils/errorHandling.ts
import { SuiTransactionBlockResponse } from '@mysten/sui/client';

export function handleTransactionResult(result: SuiTransactionBlockResponse) {
  if (result.effects?.status?.status === 'success') {
    console.log('Transaction successful:', result.digest);
    return result;
  } else {
    const error = result.effects?.status?.error;
    console.error('Transaction failed:', error);
    
    // Log to monitoring service
    if (process.env.NODE_ENV === 'production') {
      // logToMonitoringService('transaction_error', { error, digest: result.digest });
    }
    
    throw new Error(`Transaction failed: ${error}`);
  }
}
```

## Best Practices Summary

1. **Type Safety**: Use TypeScript for all contract interactions
2. **Error Handling**: Implement comprehensive error handling for all Move calls
3. **Testing**: Write both unit and integration tests for contract logic
4. **Event Monitoring**: Set up proper event listening for real-time updates
5. **Performance**: Use React Query for efficient data fetching and caching
6. **Security**: Validate all inputs before sending to smart contracts
7. **Documentation**: Document all contract interfaces and expected behaviors

## Next Steps

- Implement performance optimization techniques for advanced patterns
- Learn about [Multi-Chain dApp Architecture](../multi-chain-architecture.md) for cross-chain applications
- Follow security best practices for production deployment

## Resources

- [Sui Move Documentation](https://docs.sui.io/concepts/sui-move-concepts)
- [Move Book](https://move-language.github.io/move/)
- [Sui TypeScript SDK](https://sdk.mystenlabs.com/)
- [bex.co Sui API](https://bex.co/api-marketplace/sui)