Skip to main content
The Sign Message screen is displayed when your application requests a wallet signature for an arbitrary message. This screen shows users the message content and allows them to approve or reject the signature request.
Message signing is commonly used for authentication, proving wallet ownership, or authorizing actions without sending a blockchain transaction.

How it works

When your application requests a message signature:
  1. Your app calls the sign message method from the MoonKey SDK
  2. The Sign Message screen appears, displaying the message content
  3. User reviews the message details
  4. User approves or rejects the signature request
  5. If approved, the signed message is returned to your application

Triggering the Sign Message screen

Use the useSignMessage hook from the MoonKey SDK to trigger the Sign Message screen:
  • Ethereum
  • Solana
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';

export default function SignMessageButton() {
  const { user } = useMoonKey();
  const { signMessage } = useSignMessage();
  
  const handleSign = async () => {
    if (!user?.wallet) {
      alert('No wallet found');
      return;
    }
    
    try {
      const result = await signMessage({
        message: 'Welcome to MyApp! Sign this message to prove you own this wallet.',
        wallet: user.wallet,
        options: {
          uiOptions: {
            title: 'Sign Message',
            description: 'Please sign this message to continue',
            buttonText: 'Sign',
            showWalletUI: true
          }
        }
      });
      console.log('Signature:', result.signature);
    } catch (error) {
      console.error('User rejected signature:', error);
    }
  };
  
  return <button onClick={handleSign}>Sign Message</button>;
}

Customizing the Sign Message screen

You can customize the Sign Message screen by passing UI options in the options.uiOptions object when calling signMessage:
const result = await signMessage({
  message: 'Your message content here',
  wallet: user.wallet,
  options: {
    uiOptions: {
      title: 'Verify Wallet Ownership',
      description: 'Sign this message to prove you control this wallet address',
      buttonText: 'Sign & Verify',
      showWalletUI: true
    }
  }
});

Configuration options

Basic UI Options

title
string
Custom title text displayed at the top of the Sign Message screen.Example:
options: {
  uiOptions: {
    title: 'Verify Your Identity'
  }
}
description
string
Custom description text displayed below the title, providing context for the signature request.Example:
options: {
  uiOptions: {
    description: 'Sign this message to prove you own this wallet and access your account'
  }
}
buttonText
string
Custom text for the confirmation button.Default: 'Sign'Example:
options: {
  uiOptions: {
    buttonText: 'Approve & Sign'
  }
}
showWalletUI
boolean
Whether to show wallet information (address, balance) in the Sign Message screen.Default: trueExample:
options: {
  uiOptions: {
    showWalletUI: false // Hide wallet details for a cleaner UI
  }
}

Solana-specific Options

commitment
TransactionCommitment
The commitment level to use for Solana transactions.Available values: 'processed', 'confirmed', 'finalized'Example:
options: {
  uiOptions: {
    commitment: 'confirmed'
  }
}
encoding
TransactionEncoding
The encoding format for the Solana transaction.Available values: 'base58', 'base64'Example:
options: {
  uiOptions: {
    encoding: 'base64'
  }
}

Common use cases

Authentication verification

Prove wallet ownership for authentication:
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';

export default function VerifyWallet() {
  const { user } = useMoonKey();
  const { signMessage } = useSignMessage();
  
  const verifyOwnership = async () => {
    if (!user?.wallet) return;
    
    try {
      const result = await signMessage({
        message: `Sign this message to verify you own this wallet.\n\nTimestamp: ${Date.now()}`,
        wallet: user.wallet,
        options: {
          uiOptions: {
            title: 'Verify Wallet Ownership',
            description: 'Please sign to confirm you control this wallet',
            buttonText: 'Verify',
            showWalletUI: true
          }
        }
      });
      
      // Send signature to your backend for verification
      await fetch('/api/verify-wallet', {
        method: 'POST',
        body: JSON.stringify({ signature: result.signature })
      });
      
      alert('Wallet verified successfully!');
    } catch (error) {
      console.error('Verification failed:', error);
    }
  };
  
  return <button onClick={verifyOwnership}>Verify Wallet</button>;
}

Third-party service login

Sign in to external services with wallet signature:
const loginToThirdParty = async () => {
  if (!user?.wallet) return;
  
  const nonce = await fetchNonceFromService();
  
  const result = await signMessage({
    message: `Sign in to ThirdPartyApp\n\nNonce: ${nonce}`,
    wallet: user.wallet,
    options: {
      uiOptions: {
        title: 'Sign In to ThirdPartyApp',
        description: 'Authorize connection to ThirdPartyApp with your wallet',
        buttonText: 'Sign In',
        showWalletUI: true
      }
    }
  });
  
  // Send signature to third-party service
  await authenticateWithService(result.signature);
};

Action authorization

Authorize specific actions without gas fees:
const authorizeAction = async (actionType: string, details: string) => {
  if (!user?.wallet) return;
  
  const result = await signMessage({
    message: `Authorize Action: ${actionType}\n\nDetails: ${details}\n\nTimestamp: ${Date.now()}`,
    wallet: user.wallet,
    options: {
      uiOptions: {
        title: `Authorize ${actionType}`,
        description: 'Sign this message to authorize the action',
        buttonText: 'Authorize',
        showWalletUI: false
      }
    }
  });
  
  return result.signature;
};

Terms and conditions acceptance

Cryptographically sign terms acceptance:
const acceptTerms = async () => {
  if (!user?.wallet) return;
  
  const result = await signMessage({
    message: `I accept the Terms of Service and Privacy Policy of MyApp.\n\nVersion: 2.0\nDate: ${new Date().toISOString()}`,
    wallet: user.wallet,
    options: {
      uiOptions: {
        title: 'Accept Terms of Service',
        description: 'Sign to acknowledge that you have read and agree to our terms',
        buttonText: 'I Accept',
        showWalletUI: false
      }
    }
  });
  
  // Store signature as proof of acceptance
  await storeTermsAcceptance(result.signature);
};

Complete example

Here’s a complete example with custom UI configuration:
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';

export default function SignMessageDemo() {
  const { user, isAuthenticated } = useMoonKey();
  const { signMessage } = useSignMessage();
  const [signature, setSignature] = useState<string | null>(null);
  
  if (!isAuthenticated) {
    return <div>Please sign in first</div>;
  }
  
  if (!user?.wallet) {
    return <div>No wallet found</div>;
  }
  
  const handleSignMessage = async () => {
    try {
      const result = await signMessage({
        message: `Welcome to MyApp!\n\nWallet: ${user.wallet.address}\nTimestamp: ${Date.now()}\n\nSign this message to verify your identity.`,
        wallet: user.wallet,
        options: {
          uiOptions: {
            title: 'Verify Your Identity',
            description: 'Sign this message to prove you control this wallet',
            buttonText: 'Sign & Verify',
            showWalletUI: true
          }
        }
      });
      
      setSignature(result.signature);
      console.log('Message signed successfully:', result.signature);
    } catch (error) {
      console.error('Failed to sign message:', error);
      alert('Signature rejected or failed');
    }
  };
  
  return (
    <div>
      <h2>Sign Message Example</h2>
      <p>Wallet: {user.wallet.address}</p>
      
      <button onClick={handleSignMessage}>
        Sign Message
      </button>
      
      {signature && (
        <div style={{ marginTop: '20px' }}>
          <h3>Signature:</h3>
          <pre style={{ 
            background: '#f5f5f5', 
            padding: '10px', 
            borderRadius: '5px',
            overflow: 'auto'
          }}>
            {signature}
          </pre>
        </div>
      )}
    </div>
  );
}

User experience flow

1

Trigger signature request

Your application calls signMessage() with a message, wallet, and optional UI options.
2

Sign Message screen appears

The customized Sign Message screen is displayed to the user.
3

Review message

User reviews the message content, title, and description.
4

Approve or reject

User clicks the confirm button to sign, or cancels/closes the modal to reject.
5

Signature created

If approved, MoonKey signs the message with the user’s wallet private key.
6

Return to application

The signature is returned to your application as an object containing the signature string (Ethereum) or Uint8Array (Solana).

Message signing vs. transaction signing

It’s important to understand the difference:
  • Signs arbitrary text or data
  • Does not cost gas fees
  • Does not modify blockchain state
  • Used for authentication, verification, or authorization
  • Instant and free
  • Example: “Sign this message to log in”
  • Signs blockchain transactions
  • Costs gas fees (requires native currency)
  • Modifies blockchain state (transfers tokens, calls contracts, etc.)
  • Used for actual blockchain operations
  • Requires network confirmation
  • Example: “Sign this transaction to send 1 ETH”

Best practices

Always include a clear description explaining why the user needs to sign:
options: {
  uiOptions: {
    title: 'Verify Wallet',
    description: 'Sign this message to prove you own this wallet. This will not cost any gas.'
  }
}
Prevent replay attacks by including unique data:
message: `Action: Login\nTimestamp: ${Date.now()}\nNonce: ${randomNonce}`
Make it clear what the user is signing:
options: {
  uiOptions: {
    buttonText: 'Sign & Verify' // Better than just "Sign"
  }
}
Users may decline to sign. Always handle errors:
try {
  const result = await signMessage({ 
    message: '...', 
    wallet: user.wallet 
  });
  console.log('Signature:', result.signature);
} catch (error) {
  // User rejected - show friendly message
  toast.error('Signature required to continue');
}
Use showWalletUI: true when wallet context is important:
options: {
  uiOptions: {
    showWalletUI: true // Show address and balance
  }
}
Set to false for a cleaner UI when wallet details aren’t relevant.
Users are more likely to read and understand shorter messages:
  • Use clear, simple language
  • Break long messages into lines with \n
  • Highlight important information
Always verify the user has a wallet before attempting to sign:
if (!user?.wallet) {
  alert('Please create a wallet first');
  return;
}

Global appearance settings

The Sign Message screen also respects global appearance settings from MoonKeyProvider:
<MoonKeyProvider
  publishableKey="your_publishable_key"
  config={{
    appearance: {
      logo: 'https://myapp.com/logo.png', // Displayed on all UI components
      hideClose: false // Allow users to cancel signing
    }
  }}
>
  {children}
</MoonKeyProvider>
Learn more about configuring appearance.

Security considerations

Never sign messages without reviewing their content. Malicious applications could trick users into signing messages that authorize unwanted actions.
For developers:
  1. Verify signatures server-side - Always validate signatures on your backend before trusting them
  2. Use nonces - Include unique identifiers to prevent replay attacks
  3. Include context - Clearly state what the signature authorizes
  4. Set expiration - Include timestamps and reject old signatures
  5. Validate message format - Ensure the signed message matches your expected format
For users: The Sign Message screen helps users by:
  • Displaying the full message content
  • Showing their wallet address
  • Providing clear context via title and description
  • Allowing them to reject the request

Verifying signatures

After obtaining a signature, you typically want to verify it on your backend:
  • Ethereum
  • Solana
import { verifyMessage } from 'viem';

// On your frontend, after signing
const result = await signMessage({
  message: 'Your message',
  wallet: user.wallet
});

// Send result.signature to your backend
const response = await fetch('/api/verify', {
  method: 'POST',
  body: JSON.stringify({
    message: 'Your message',
    signature: result.signature,
    address: user.wallet.address
  })
});

// On your backend
const isValid = await verifyMessage({
  address: userWalletAddress,
  message: originalMessage,
  signature: receivedSignature
});

if (isValid) {
  // Signature is valid - user owns the wallet
  console.log('Signature verified!');
}

Next steps