> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streambird.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign Message

Sign arbitrary messages with a user's embedded Ethereum wallet using the `personal_sign` method. Message signing is commonly used for authentication, proof of ownership, and off-chain authorization.

<Info>
  This method uses Ethereum's `personal_sign` RPC method, which prefixes messages with `"\x19Ethereum Signed Message:\n"` before signing. For raw hash signatures, see [Sign Raw Hash](/wallet-as-a-service/using-wallets/ethereum/sign-raw-hash).
</Info>

## Overview

The `useSignMessage` hook from MoonKey's React SDK provides a simple interface for signing messages. Common use cases include:

* **User authentication**: Prove wallet ownership without passwords
* **Off-chain authorization**: Sign permissions without gas fees
* **Proof of ownership**: Verify control of an Ethereum address
* **Session tokens**: Generate authenticated session credentials

## React SDK

<Tabs>
  <Tab title="Basic Usage">
    To sign a message, use the `signMessage` method from the `useSignMessage` hook:

    ```tsx theme={null}
    import { useMoonKey } from '@moon-key/react-auth';
    import { useSignMessage } from '@moon-key/react-auth/ethereum';

    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 My dApp! Sign this message to authenticate.'
          });

          console.log('Message signed:', result.signature);
          alert('Message signed successfully!');
        } catch (error) {
          console.error('Signing failed:', error);
        }
      };

      return (
        <button onClick={handleSign}>
          Sign Message
        </button>
      );
    }
    ```
  </Tab>

  <Tab title="With Callbacks">
    Use callbacks to handle success and error cases:

    ```tsx theme={null}
    import { useMoonKey } from '@moon-key/react-auth';
    import { useSignMessage } from '@moon-key/react-auth/ethereum';

    function SignWithCallbacks() {
      const { user } = useMoonKey();
      
      const { signMessage } = useSignMessage({
        onSuccess: ({ signature }) => {
          console.log('Signature:', signature);
          alert('Successfully signed message!');
          // Submit signature to your backend
          fetch('/api/verify-signature', {
            method: 'POST',
            body: JSON.stringify({ signature })
          });
        },
        onError: (error) => {
          console.error('Signing error:', error);
          alert('Failed to sign message');
        }
      });

      const handleSign = async () => {
        if (!user?.wallet) return;

        await signMessage({
          message: 'Authenticate with My App'
        });
      };

      return (
        <button onClick={handleSign}>
          Sign to Authenticate
        </button>
      );
    }
    ```
  </Tab>

  <Tab title="With UI Customization">
    Customize the signing modal:

    ```tsx theme={null}
    import { useMoonKey } from '@moon-key/react-auth';
    import { useSignMessage } from '@moon-key/react-auth/ethereum';

    function CustomSignButton() {
      const { user } = useMoonKey();
      const { signMessage } = useSignMessage();

      const handleSign = async () => {
        if (!user?.wallet) return;

        try {
          const result = await signMessage({
            message: 'I vote for Proposal #42'
          }, {
            uiConfig: {
              title: 'Sign Your Vote',
              description: 'You are voting for Proposal #42',
              confirmButtonText: 'Sign Vote',
              cancelButtonText: 'Cancel'
            }
          });

          console.log('Vote signed:', result.signature);
        } catch (error) {
          console.error('Vote signing failed:', error);
        }
      };

      return (
        <button onClick={handleSign}>
          Vote on Proposal
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

## Parameters

### Message Object

The first parameter to `signMessage`:

<ParamField path="message" type="string" required>
  The message to sign with the wallet. Can be any string.

  **Example:**

  ```tsx theme={null}
  message: 'Welcome to My dApp! Please sign to authenticate.'
  ```
</ParamField>

### Options Object

The second parameter is an optional configuration object:

<ParamField path="uiConfig" type="object">
  Configuration for the signing confirmation modal UI.

  <Expandable title="uiConfig properties">
    <ParamField path="title" type="string">
      Custom title for the confirmation modal.
    </ParamField>

    <ParamField path="description" type="string">
      Description text shown in the modal.
    </ParamField>

    <ParamField path="confirmButtonText" type="string">
      Text for the confirm button.
    </ParamField>

    <ParamField path="cancelButtonText" type="string">
      Text for the cancel button.
    </ParamField>

    <ParamField path="showWalletUI" type="boolean">
      Whether to show the confirmation modal. Set to `false` to sign without confirmation.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="wallet" type="Wallet">
  Specific wallet to use for signing. If not provided, uses the user's default wallet.

  **Example:**

  ```tsx theme={null}
  wallet: user.wallet
  ```
</ParamField>

### Hook Callbacks

Configure callbacks when initializing the hook:

<ParamField path="onSuccess" type="(result: { signature: string }) => void">
  Callback executed after a user successfully signs a message. Receives the signature.

  **Example:**

  ```tsx theme={null}
  const { signMessage } = useSignMessage({
    onSuccess: ({ signature }) => {
      console.log('Signed:', signature);
      submitToBackend(signature);
    }
  });
  ```
</ParamField>

<ParamField path="onError" type="(error: Error) => void">
  Callback executed if signing fails or user cancels. Receives the error.

  **Example:**

  ```tsx theme={null}
  const { signMessage } = useSignMessage({
    onError: (error) => {
      console.error('Failed:', error);
      showErrorNotification(error.message);
    }
  });
  ```
</ParamField>

## Returns

The `signMessage` method returns a Promise that resolves with:

<ResponseField name="signature" type="string">
  The hex-encoded signature produced by the wallet.

  **Example:**

  ```tsx theme={null}
  "0x9c29f0c3a6f3e55f8f7b5e9f8c1b8e2a4d5c6b7a8e9f0c1b2a3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1b"
  ```
</ResponseField>

<ResponseField name="encoding" type="'hex'">
  The encoding format of the signature. Currently always `'hex'` for Ethereum.
</ResponseField>

## Complete Examples

### User Authentication

```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';

export default function AuthenticateWithSignature() {
  const { user, isAuthenticated } = useMoonKey();
  const [isAuthenticating, setIsAuthenticating] = useState(false);
  const [authToken, setAuthToken] = useState<string | null>(null);

  const { signMessage } = useSignMessage({
    onSuccess: async ({ signature }) => {
      console.log('Signature obtained:', signature);

      // Send signature to backend for verification
      try {
        const response = await fetch('/api/auth/verify', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            address: user?.wallet?.address,
            signature,
            message: 'Sign in to My dApp'
          })
        });

        const data = await response.json();
        setAuthToken(data.token);
        alert('Authentication successful!');
      } catch (error) {
        console.error('Backend verification failed:', error);
        alert('Authentication failed');
      } finally {
        setIsAuthenticating(false);
      }
    },
    onError: (error) => {
      console.error('Signing failed:', error);
      alert('Authentication cancelled or failed');
      setIsAuthenticating(false);
    }
  });

  const handleAuthenticate = async () => {
    if (!user?.wallet) {
      alert('Please connect your wallet first');
      return;
    }

    setIsAuthenticating(true);

    await signMessage({
      message: 'Sign in to My dApp'
    }, {
      uiConfig: {
        title: 'Sign In',
        description: 'Sign this message to authenticate with My dApp',
        confirmButtonText: 'Sign In'
      }
    });
  };

  if (!isAuthenticated) {
    return <p>Please sign in first</p>;
  }

  return (
    <div className="auth-container">
      <h2>Wallet Authentication</h2>
      
      {!authToken ? (
        <button
          onClick={handleAuthenticate}
          disabled={isAuthenticating}
          className="auth-button"
        >
          {isAuthenticating ? 'Authenticating...' : 'Sign to Authenticate'}
        </button>
      ) : (
        <div className="authenticated">
          <p>✅ Authenticated</p>
          <p className="token">Token: {authToken.slice(0, 20)}...</p>
        </div>
      )}
    </div>
  );
}
```

### Session Authorization

```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
import { useState, useEffect } from 'react';

export default function SessionAuthorization() {
  const { user } = useMoonKey();
  const [sessionToken, setSessionToken] = useState<string | null>(null);
  const [expiresAt, setExpiresAt] = useState<Date | null>(null);

  const { signMessage } = useSignMessage({
    onSuccess: async ({ signature }) => {
      // Create session on backend
      try {
        const response = await fetch('/api/session/create', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            address: user?.wallet?.address,
            signature,
            timestamp: Date.now()
          })
        });

        const data = await response.json();
        setSessionToken(data.sessionToken);
        setExpiresAt(new Date(data.expiresAt));

        // Store in localStorage
        localStorage.setItem('sessionToken', data.sessionToken);
      } catch (error) {
        console.error('Session creation failed:', error);
      }
    }
  });

  const createSession = async () => {
    if (!user?.wallet) return;

    const timestamp = Date.now();
    const message = `Create session at ${timestamp}`;

    await signMessage({
      message
    }, {
      uiConfig: {
        title: 'Create Session',
        description: 'Sign to create an authenticated session',
        confirmButtonText: 'Create Session'
      }
    });
  };

  const revokeSession = () => {
    setSessionToken(null);
    setExpiresAt(null);
    localStorage.removeItem('sessionToken');
  };

  // Check for existing session on mount
  useEffect(() => {
    const stored = localStorage.getItem('sessionToken');
    if (stored) {
      setSessionToken(stored);
    }
  }, []);

  return (
    <div className="session-auth">
      <h2>Session Authorization</h2>

      {!sessionToken ? (
        <div>
          <p>No active session</p>
          <button onClick={createSession}>
            Create Session
          </button>
        </div>
      ) : (
        <div className="active-session">
          <p>✅ Session Active</p>
          <p className="session-token">Token: {sessionToken.slice(0, 20)}...</p>
          {expiresAt && (
            <p className="expires">Expires: {expiresAt.toLocaleString()}</p>
          )}
          <button onClick={revokeSession} className="revoke-button">
            Revoke Session
          </button>
        </div>
      )}
    </div>
  );
}
```

### Proof of Ownership

```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
import { verifyMessage } from 'viem';

export default function ProofOfOwnership() {
  const { user } = useMoonKey();
  const [signature, setSignature] = useState<string>('');
  const [isVerified, setIsVerified] = useState<boolean | null>(null);

  const { signMessage } = useSignMessage({
    onSuccess: ({ signature }) => {
      setSignature(signature);
      console.log('Signature:', signature);
    }
  });

  const handleProve = async () => {
    if (!user?.wallet) return;

    const message = `I own address ${user.wallet.address}`;

    await signMessage({
      message
    }, {
      uiConfig: {
        title: 'Prove Ownership',
        description: 'Sign to prove you own this wallet address',
        confirmButtonText: 'Sign Proof'
      }
    });
  };

  const handleVerify = async () => {
    if (!signature || !user?.wallet) return;

    try {
      const message = `I own address ${user.wallet.address}`;

      // Verify signature using viem
      const isValid = await verifyMessage({
        address: user.wallet.address as `0x${string}`,
        message,
        signature: signature as `0x${string}`
      });

      setIsVerified(isValid);
    } catch (error) {
      console.error('Verification failed:', error);
      setIsVerified(false);
    }
  };

  return (
    <div className="proof-ownership">
      <h2>Prove Wallet Ownership</h2>

      <div className="wallet-info">
        <p>Wallet: {user?.wallet?.address}</p>
      </div>

      <button onClick={handleProve} disabled={!user?.wallet}>
        Generate Proof
      </button>

      {signature && (
        <div className="signature-section">
          <h3>Signature Generated</h3>
          <p className="signature">{signature.slice(0, 40)}...</p>

          <button onClick={handleVerify}>
            Verify Signature
          </button>

          {isVerified !== null && (
            <p className={isVerified ? 'verified' : 'invalid'}>
              {isVerified ? '✅ Signature Valid' : '❌ Signature Invalid'}
            </p>
          )}
        </div>
      )}
    </div>
  );
}
```

## Verifying Signatures

After obtaining a signature, you can verify it using `viem`:

```tsx theme={null}
import { verifyMessage } from 'viem';

async function verifySignature(
  address: string,
  message: string,
  signature: string
): Promise<boolean> {
  try {
    const isValid = await verifyMessage({
      address: address as `0x${string}`,
      message,
      signature: signature as `0x${string}`
    });

    return isValid;
  } catch (error) {
    console.error('Verification failed:', error);
    return false;
  }
}

// Usage
const isValid = await verifySignature(
  '0xUserWalletAddress...',
  'Welcome to My dApp!',
  '0x9c29f0c3a6f3e55f8f7b5e9f...'
);

if (isValid) {
  console.log('Signature is valid!');
} else {
  console.log('Signature is invalid!');
}
```

### Backend Verification (Node.js)

```typescript theme={null}
// backend/verify.ts
import { verifyMessage } from 'viem';

export async function verifyUserSignature(
  address: string,
  message: string,
  signature: string
): Promise<{ valid: boolean; error?: string }> {
  try {
    const isValid = await verifyMessage({
      address: address as `0x${string}`,
      message,
      signature: signature as `0x${string}`
    });

    return { valid: isValid };
  } catch (error: any) {
    return {
      valid: false,
      error: error.message
    };
  }
}

// API route example
app.post('/api/verify-signature', async (req, res) => {
  const { address, message, signature } = req.body;

  const result = await verifyUserSignature(address, message, signature);

  if (result.valid) {
    // Generate JWT or session token
    const token = generateAuthToken(address);
    res.json({ success: true, token });
  } else {
    res.status(401).json({ success: false, error: result.error });
  }
});
```

## UI Customization

Customize the signing modal to match your brand:

```tsx theme={null}
import { useSignMessage } from '@moon-key/react-auth/ethereum';

const { signMessage } = useSignMessage();

await signMessage({
  message: 'Sign this message'
}, {
  uiConfig: {
    title: 'Verify Your Identity',
    description: 'Sign this message to prove wallet ownership',
    confirmButtonText: 'Sign Message',
    cancelButtonText: 'Cancel'
  }
});
```

### Hide Modal

For silent signing (ensure user has given explicit consent):

```tsx theme={null}
await signMessage({
  message: 'Background signature'
}, {
  uiConfig: {
    showWalletUI: false
  }
});
```

<Warning>
  Hiding the signing modal removes the user's ability to review the message before signing. Only use this for trusted operations or after obtaining explicit user consent.
</Warning>

## Message Formatting Best Practices

<AccordionGroup>
  <Accordion title="Use clear, human-readable messages">
    Always make messages understandable to users:

    ```tsx theme={null}
    // ✅ Good - Clear and specific
    await signMessage({
      message: 'Sign in to My dApp on December 15, 2024'
    });

    // ❌ Bad - Cryptic or unclear
    await signMessage({
      message: 'auth:1234567890:xyz'
    });
    ```
  </Accordion>

  <Accordion title="Include context and purpose">
    Explain what the signature will be used for:

    ```tsx theme={null}
    await signMessage({
      message: `Sign this message to authenticate with My dApp.
      
    Address: ${walletAddress}
    Timestamp: ${Date.now()}
    Action: Login

    This signature will not cost any gas fees.`
    });
    ```
  </Accordion>

  <Accordion title="Add timestamps to prevent replay attacks">
    Include timestamps or nonces to prevent signature replay:

    ```tsx theme={null}
    const timestamp = Date.now();
    const nonce = generateNonce();

    await signMessage({
      message: `Authenticate at ${timestamp} with nonce ${nonce}`
    });

    // Verify timestamp is recent on backend
    const isRecent = Date.now() - timestamp < 60000; // 1 minute
    ```
  </Accordion>

  <Accordion title="Specify the domain or app">
    Include your app's domain to prevent phishing:

    ```tsx theme={null}
    await signMessage({
      message: `Sign in to myapp.com
      
    This request is from: https://myapp.com
    Time: ${new Date().toISOString()}
    Action: Authentication`
    });
    ```
  </Accordion>

  <Accordion title="Use structured data for complex operations">
    For structured data, consider using EIP-712 (Sign Typed Data):

    ```tsx theme={null}
    // For simple auth, use personal_sign
    await signMessage({ message: 'Sign in to My App' });

    // For complex structured data, use signTypedData instead
    // See: /wallet-as-a-service/using-wallets/ethereum/sign-typed-data
    ```
  </Accordion>
</AccordionGroup>

## Error Handling

Handle common signing errors gracefully:

```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';

function SignWithErrorHandling() {
  const { user } = useMoonKey();
  
  const { signMessage } = useSignMessage({
    onSuccess: ({ signature }) => {
      console.log('Success:', signature);
      // Handle success
    },
    onError: (error: any) => {
      if (error.code === 'USER_REJECTED') {
        console.log('User cancelled signing');
        // Don't show error - user deliberately cancelled
      } else if (error.code === 'INVALID_MESSAGE') {
        console.log('Invalid message format');
        alert('The message format is invalid');
      } else if (error.code === 'NETWORK_ERROR') {
        console.log('Network error during signing');
        alert('Network error. Please try again');
      } else {
        console.error('Signing failed:', error);
        alert('Failed to sign message: ' + error.message);
      }
    }
  });

  const handleSign = async () => {
    if (!user?.wallet) return;

    await signMessage({
      message: 'Sign this message'
    });
  };

  return <button onClick={handleSign}>Sign Message</button>;
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always validate on the backend">
    Never trust client-side signature verification alone:

    ```tsx theme={null}
    // ❌ Bad - Only client-side verification
    const isValid = await verifyMessage({ address, message, signature });
    if (isValid) {
      setAuthenticated(true); // Insecure!
    }

    // ✅ Good - Verify on backend
    const response = await fetch('/api/verify-signature', {
      method: 'POST',
      body: JSON.stringify({ address, message, signature })
    });
    const { valid, token } = await response.json();
    if (valid) {
      setAuthToken(token); // Secure!
    }
    ```
  </Accordion>

  <Accordion title="Store signatures securely">
    If storing signatures, ensure proper security:

    ```tsx theme={null}
    // ❌ Don't store in localStorage without encryption
    localStorage.setItem('signature', signature);

    // ✅ Store session tokens instead
    localStorage.setItem('sessionToken', jwtToken);

    // ✅ Or store on secure backend
    await fetch('/api/sessions', {
      method: 'POST',
      body: JSON.stringify({ signature, address })
    });
    ```
  </Accordion>

  <Accordion title="Implement signature expiration">
    Add expiration to prevent old signatures from being reused:

    ```tsx theme={null}
    const expiresAt = Date.now() + (5 * 60 * 1000); // 5 minutes
    const message = `Sign in to My App

    Expires: ${expiresAt}
    Address: ${address}`;

    // Backend verification
    if (Date.now() > expiresAt) {
      return { valid: false, error: 'Signature expired' };
    }
    ```
  </Accordion>

  <Accordion title="Provide clear user feedback">
    Keep users informed throughout the signing process:

    ```tsx theme={null}
    const [status, setStatus] = useState<'idle' | 'signing' | 'verifying' | 'success' | 'error'>('idle');

    const handleSign = async () => {
      setStatus('signing');

      const { signature } = await signMessage({ message: 'Sign in' });

      setStatus('verifying');

      const response = await fetch('/api/verify', {
        method: 'POST',
        body: JSON.stringify({ signature })
      });

      if (response.ok) {
        setStatus('success');
      } else {
        setStatus('error');
      }
    };
    ```
  </Accordion>

  <Accordion title="Handle user cancellations gracefully">
    Don't show errors when users deliberately cancel:

    ```tsx theme={null}
    const { signMessage } = useSignMessage({
      onError: (error: any) => {
        if (error.code === 'USER_REJECTED') {
          // User cancelled - this is expected behavior
          console.log('User cancelled signing');
          return;
        }
        // Show error for other cases
        alert('Signing failed: ' + error.message);
      }
    });
    ```
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Signature verification fails">
    Common causes and solutions:

    * **Wrong message**: Ensure the exact same message is used for verification
    * **Address mismatch**: Verify the correct wallet address is used
    * **Encoding issues**: Message must be UTF-8 encoded string
    * **Wrong verification method**: Use `verifyMessage` for `personal_sign`

    ```tsx theme={null}
    // Ensure exact message match
    const originalMessage = 'Sign in to My App';

    // Sign
    const { signature } = await signMessage({ message: originalMessage });

    // Verify with EXACT same message
    const isValid = await verifyMessage({
      address: walletAddress as `0x${string}`,
      message: originalMessage, // Must match exactly
      signature: signature as `0x${string}`
    });
    ```
  </Accordion>

  <Accordion title="User cancelled signing">
    Handle cancellations without showing errors:

    ```tsx theme={null}
    const { signMessage } = useSignMessage({
      onError: (error: any) => {
        if (error.code === 'USER_REJECTED' || error.message?.includes('User rejected')) {
          // Expected behavior - don't show error
          return;
        }
        // Show error for unexpected failures
        showErrorToast(error.message);
      }
    });
    ```
  </Accordion>

  <Accordion title="Message encoding issues">
    Ensure proper message encoding:

    ```tsx theme={null}
    // ✅ Use UTF-8 strings
    await signMessage({
      message: 'Hello, world!' // UTF-8 string
    });

    // ❌ Don't pass raw bytes or hex
    // await signMessage({
    //   message: '0x48656c6c6f' // This won't work
    // });

    // For hex or bytes, decode first
    const hexMessage = '0x48656c6c6f';
    const decodedMessage = Buffer.from(hexMessage.slice(2), 'hex').toString('utf-8');
    await signMessage({ message: decodedMessage });
    ```
  </Accordion>

  <Accordion title="Callback not firing">
    Ensure callbacks are properly configured:

    ```tsx theme={null}
    // ✅ Configure callbacks on hook initialization
    const { signMessage } = useSignMessage({
      onSuccess: ({ signature }) => {
        console.log('Success:', signature);
      },
      onError: (error) => {
        console.log('Error:', error);
      }
    });

    // Then call signMessage
    await signMessage({ message: 'Test' });

    // ❌ Don't try to pass callbacks to signMessage directly
    // await signMessage({ message: 'Test' }, { onSuccess: ... }); // Won't work
    ```
  </Accordion>
</AccordionGroup>

## Related Documentation

<CardGroup cols={2}>
  <Card title="Sign Typed Data" icon="file-signature" href="/wallet-as-a-service/using-wallets/ethereum/sign-typed-data">
    Sign structured EIP-712 data
  </Card>

  <Card title="Sign Transaction" icon="pen-to-square" href="/wallet-as-a-service/using-wallets/ethereum/sign-transaction">
    Sign transactions without broadcasting
  </Card>

  <Card title="Sign Message UI" icon="window" href="/wallet-as-a-service/ui-components/sign-message">
    Customize the signing modal
  </Card>

  <Card title="Send Transaction" icon="paper-plane" href="/wallet-as-a-service/using-wallets/ethereum/send-transaction">
    Sign and broadcast transactions
  </Card>
</CardGroup>

## Next Steps

* Learn about [signing typed data](/wallet-as-a-service/using-wallets/ethereum/sign-typed-data) for structured messages
* Explore [signature verification](https://viem.sh/docs/utilities/verifyMessage) with viem
* Understand [authentication patterns](/authentication/user-authentication/authentication-methods/email) with MoonKey
* Customize the [signing UI](/wallet-as-a-service/ui-components/sign-message) to match your brand
