Cycle

The Scriptorium

Smart Assembly code templates and tools for on-chain development in Eve Frontier.

SUI Wallet Authentication
BeginnerChapter 1 of 415 min read

Wallet Connection Flow

Connecting to a SUI wallet is the first step in any dApp. This chapter covers the SUI wallet standard, auto-discovery of browser wallets, and the provider hierarchy needed for React dApps.

The SUI Wallet Standard

SUI uses the [Wallet Standard](https://docs.sui.io/guides/developer/sui-wallet-standard) -- a specification that allows dApps to discover and interact with wallets without knowing the specific wallet implementation. Any wallet that implements the standard (EVE Vault, Sui Wallet, etc.) will automatically appear in your dApp.

Provider Setup

The wallet context must wrap your entire application. The AncientStorage dApp uses a lazy-loaded provider to avoid SSR crashes:

typescript
'use client';

import { Component, useEffect, useState } from 'react';

type SuiNetwork = 'mainnet' | 'testnet';

const DEFAULT_NETWORK: SuiNetwork =
  (process.env.NEXT_PUBLIC_SUI_NETWORK as SuiNetwork) ?? 'testnet';

const FULLNODE_URLS: Record<SuiNetwork, string> = {
  mainnet: 'https://fullnode.mainnet.sui.io:443',
  testnet: 'https://fullnode.testnet.sui.io:443',
};

export function SuiWalletProvider({
  children,
}: {
  children: React.ReactNode;
}): React.ReactElement {
  const [suiModules, setSuiModules] = useState(null);

  useEffect(() => {
    let cancelled = false;

    Promise.all([
      import('@mysten/dapp-kit-core'),
      import('@mysten/dapp-kit-react'),
      import('@mysten/sui/grpc'),
    ])
      .then(([coreModule, reactModule, grpcModule]) => {
        if (cancelled) return;
        const kit = coreModule.createDAppKit({
          networks: [DEFAULT_NETWORK],
          createClient: (network: string) =>
            new grpcModule.SuiGrpcClient({
              network: network || DEFAULT_NETWORK,
              baseUrl: FULLNODE_URLS[network] ?? FULLNODE_URLS[DEFAULT_NETWORK],
            }),
          defaultNetwork: DEFAULT_NETWORK,
          autoConnect: true,
        });
        setSuiModules({
          DAppKitProvider: reactModule.DAppKitProvider,
          dAppKit: kit,
        });
      })
      .catch(() => {
        // Module load failure is non-fatal
      });

    return () => { cancelled = true; };
  }, []);

  if (!suiModules) {
    return <>{children}</>;
  }

  const { DAppKitProvider, dAppKit } = suiModules;
  return (
    <DAppKitProvider dAppKit={dAppKit}>
      {children}
    </DAppKitProvider>
  );
}

Why Lazy Loading?

The @mysten/dapp-kit-react package accesses window at the module level, which causes crashes during server-side rendering (SSR) in Next.js. By dynamically importing the modules inside useEffect, we ensure they only load in the browser.

The createDAppKit Configuration

typescript
const kit = coreModule.createDAppKit({
  networks: [DEFAULT_NETWORK],
  createClient: (network) =>
    new SuiGrpcClient({
      network,
      baseUrl: FULLNODE_URLS[network],
    }),
  defaultNetwork: DEFAULT_NETWORK,
  autoConnect: true,
});
  • networks: Which networks your dApp supports.
  • createClient: Factory function that creates a SUI client for each network.
  • defaultNetwork: Which network to use initially.
  • autoConnect: Attempt to reconnect to the last-used wallet on page load.

Detecting Wallets

The useWallets hook returns all discovered wallets:

typescript
import { useWallets } from '@mysten/dapp-kit-react';

function WalletButton() {
  const wallets = useWallets();
  const hasWallet = wallets.length > 0;

  if (!hasWallet) {
    return (
      <button onClick={() => window.open('https://evefrontier.com/vault', '_blank')}>
        Install EVE Vault
      </button>
    );
  }

  return <button>Connect Wallet</button>;
}

Wallet discovery happens automatically through the wallet standard. When a user has EVE Vault (or any SUI-compatible wallet) installed as a browser extension, it registers itself and appears in the wallets array.

Connecting

Use useDAppKit().connectWallet() to initiate the connection:

typescript
import { useDAppKit, useWallets } from '@mysten/dapp-kit-react';

function ConnectButton() {
  const wallets = useWallets();
  const dAppKit = useDAppKit();

  const handleConnect = async () => {
    if (wallets.length === 0) return;
    await dAppKit.connectWallet({ wallet: wallets[0] });
  };

  return <button onClick={handleConnect}>Connect</button>;
}

The connectWallet call triggers the wallet's connection popup. The user approves the connection, and the dApp receives the connected account information.

Reading Connection State

The useWalletConnection hook provides the current connection state:

typescript
import { useWalletConnection } from '@mysten/dapp-kit-react';

function AccountInfo() {
  const connection = useWalletConnection();

  if (!connection.isConnected || !connection.account) {
    return <p>Not connected</p>;
  }

  const address = connection.account.address;
  const abbreviated = ${address.slice(0, 6)}...${address.slice(-4)};

  return <p>Connected: {abbreviated}</p>;
}

The connection.account object provides:

  • address -- the user's SUI address (e.g., 0x1234...abcd)
  • Other wallet-specific metadata

Disconnecting

typescript
const dAppKit = useDAppKit();

const handleDisconnect = async () => {
  await dAppKit.disconnectWallet();
};

The Complete Component

Here is the AncientStorage dApp's wallet connection button with all states:

typescript
export function WalletConnectButton({ callbackUrl = '/' }) {
  const wallets = useWallets();
  const dAppKit = useDAppKit();
  const connection = useWalletConnection();
  const hasWallet = wallets.length > 0;

  // Not connected — show connect or install button
  if (!connection.isConnected) {
    return (
      <button onClick={async () => {
        if (!hasWallet) {
          window.open('https://evefrontier.com/vault', '_blank');
          return;
        }
        await dAppKit.connectWallet({ wallet: wallets[0] });
      }}>
        {hasWallet ? 'Connect Wallet' : 'Install EVE Vault'}
      </button>
    );
  }

  // Connected but not signed in — show sign-in button
  return (
    <div>
      <button onClick={handleSignIn}>
        Sign Message to Log In
      </button>
      <span>{connection.account?.address}</span>
    </div>
  );
}

Notice the two-step flow: first connect (establish communication with the wallet), then sign in (prove identity via message signing, covered in the next chapter).

Key Takeaways

  • SUI uses the Wallet Standard for automatic wallet discovery -- any compatible wallet works.
  • Lazy-load @mysten/dapp-kit-react to avoid SSR crashes in Next.js.
  • createDAppKit configures the network, client factory, and auto-connect behavior.
  • Use useWallets() to detect installed wallets and useDAppKit().connectWallet() to connect.
  • useWalletConnection() provides the current connection state and account address.
  • Wallet connection is separate from authentication -- connecting only establishes communication, not identity.

Sign in to track your progress.