> ## 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 Message

Sign arbitrary messages with a user's embedded Solana wallet. Message signing is commonly used for authentication, proof of ownership, and off-chain authorization.

## Overview

Solana message signing allows users to cryptographically sign messages using their wallet's private key. This enables:

* **Proof of ownership**: Verify control of a Solana address
* **Authentication**: Sign messages to prove identity
* **Off-chain actions**: Authorize actions without transaction fees
* **Session tokens**: Create authenticated session credentials

<Info>
  Unlike Ethereum which uses `personal_sign`, Solana uses the `signMessage` method from the Solana Wallet Standard.
</Info>

## 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/solana';
    import bs58 from 'bs58';

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

      const handleSign = async () => {
        if (!user?.wallet) {
          alert('No wallet found');
          return;
        }

        const message = 'Hello from MoonKey on Solana!';

        try {
          const result = await signMessage({
            message: new TextEncoder().encode(message),
            wallet: user.wallet
          });

          // Convert Uint8Array signature to base58 string
          const signature = bs58.encode(result.signature);
          console.log('Message signed:', signature);
          alert(`Signature: ${signature}`);
        } catch (error) {
          console.error('Failed to sign message:', 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/solana';
    import bs58 from 'bs58';

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

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

        const message = 'Verify ownership of my Solana wallet';
        
        await signMessage({
          message: new TextEncoder().encode(message),
          wallet: user.wallet
        });
      };

      return (
        <button onClick={handleSign}>
          Verify Ownership
        </button>
      );
    }
    ```
  </Tab>

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

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

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

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

        const message = 'I agree to the terms of service';

        try {
          const result = await signMessage({
            message: new TextEncoder().encode(message),
            wallet: user.wallet
          }, {
            uiConfig: {
              title: 'Sign Agreement',
              description: 'Please sign to accept the terms of service',
              confirmButtonText: 'Sign Agreement',
              cancelButtonText: 'Cancel'
            }
          });

          const signature = bs58.encode(result.signature);
          console.log('Agreement signed:', signature);
        } catch (error) {
          console.error('Signing failed:', error);
        }
      };

      return (
        <button onClick={handleSign}>
          Accept Terms
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

## Parameters

<ParamField path="message" type="Uint8Array" required>
  The message to sign as a Uint8Array. Use `TextEncoder` to convert strings to Uint8Array.

  **Example:**

  ```tsx theme={null}
  const message = new TextEncoder().encode('Hello, Solana!');
  ```
</ParamField>

<ParamField path="wallet" type="Wallet" required>
  The Solana wallet to use for signing.

  **Example:**

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

### Options Object

The second parameter is an optional configuration object:

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

  <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>

### Hook Callbacks

<ParamField path="onSuccess" type="(result: { signature: Uint8Array; signedMessage: Uint8Array }) => void">
  Callback executed after successful signing. Receives the signature and signed message as Uint8Array.
</ParamField>

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

## Returns

<ResponseField name="signature" type="Uint8Array">
  The signature produced by the wallet as a Uint8Array. Use `bs58.encode()` to convert to a base58 string.
</ResponseField>

<ResponseField name="signedMessage" type="Uint8Array">
  The original message that was signed.
</ResponseField>

## Message Encoding

### String to Uint8Array

Convert string messages to Uint8Array using `TextEncoder`:

```tsx theme={null}
// Simple string message
const message = 'Hello, Solana!';
const encodedMessage = new TextEncoder().encode(message);

await signMessage({
  message: encodedMessage,
  wallet: user.wallet
});
```

### Structured Messages

For structured authentication messages:

```tsx theme={null}
const authMessage = `
Welcome to My dApp!

Sign this message to verify your wallet ownership.

Wallet: ${user.wallet.address}
Nonce: ${nonce}
Timestamp: ${Date.now()}
`;

const encodedMessage = new TextEncoder().encode(authMessage);

const result = await signMessage({
  message: encodedMessage,
  wallet: user.wallet
});
```

## Signature Encoding

### Convert to Base58

Solana signatures are typically represented as base58 strings:

```tsx theme={null}
import bs58 from 'bs58';

const result = await signMessage({
  message: new TextEncoder().encode('Hello!'),
  wallet: user.wallet
});

// Convert Uint8Array to base58 string
const signature = bs58.encode(result.signature);
console.log('Signature:', signature);
```

### Install bs58

<CodeGroup>
  ```bash npm theme={null}
  npm install bs58
  ```

  ```bash pnpm theme={null}
  pnpm install bs58
  ```

  ```bash yarn theme={null}
  yarn add bs58
  ```
</CodeGroup>

## Complete Examples

### User Authentication

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

export default function SolanaAuth() {
  const { user, isAuthenticated } = useMoonKey();
  const [isVerifying, setIsVerifying] = useState(false);

  const { signMessage } = useSignMessage({
    onSuccess: async ({ signature }) => {
      const sig = bs58.encode(signature);
      
      try {
        // Verify signature on backend
        const response = await fetch('/api/auth/verify-solana', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            address: user?.wallet?.address,
            signature: sig,
            message: authMessage
          })
        });

        if (response.ok) {
          alert('Authentication successful!');
        } else {
          alert('Authentication failed');
        }
      } catch (error) {
        console.error('Verification failed:', error);
      } finally {
        setIsVerifying(false);
      }
    },
    onError: (error) => {
      console.error('Signing failed:', error);
      setIsVerifying(false);
    }
  });

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

    setIsVerifying(true);

    const nonce = Math.random().toString(36).substring(7);
    const authMessage = `
Welcome to My Solana dApp!

Please sign this message to authenticate.

Wallet: ${user.wallet.address}
Nonce: ${nonce}
Timestamp: ${Date.now()}
    `.trim();

    await signMessage({
      message: new TextEncoder().encode(authMessage),
      wallet: user.wallet
    }, {
      uiConfig: {
        title: 'Authenticate',
        description: 'Sign this message to verify your wallet ownership',
        confirmButtonText: 'Sign & Authenticate'
      }
    });
  };

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

  return (
    <div className="solana-auth">
      <h2>Solana Authentication</h2>
      <p>Wallet: {user?.wallet?.address}</p>
      
      <button
        onClick={handleAuthenticate}
        disabled={isVerifying}
      >
        {isVerifying ? 'Verifying...' : 'Authenticate with Solana'}
      </button>
    </div>
  );
}
```

### Session Authorization

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

export default function SessionAuth() {
  const { user } = useMoonKey();
  const { signMessage } = useSignMessage();

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

    const sessionMessage = `
Create Session

This signature will be used to authenticate your requests.

Wallet: ${user.wallet.address}
Expires: ${new Date(Date.now() + 86400000).toISOString()}
Session ID: ${crypto.randomUUID()}
    `.trim();

    try {
      const result = await signMessage({
        message: new TextEncoder().encode(sessionMessage),
        wallet: user.wallet
      }, {
        uiConfig: {
          title: 'Create Session',
          description: 'Sign to create an authenticated session (valid for 24 hours)',
          confirmButtonText: 'Create Session'
        }
      });

      const signature = bs58.encode(result.signature);

      // Create session on backend
      const response = await fetch('/api/sessions/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          address: user.wallet.address,
          signature,
          message: sessionMessage
        })
      });

      const { sessionToken } = await response.json();
      
      // Store session token
      localStorage.setItem('sessionToken', sessionToken);
      alert('Session created successfully!');
    } catch (error) {
      console.error('Session creation failed:', error);
    }
  };

  return (
    <button onClick={createSession}>
      Create Authenticated Session
    </button>
  );
}
```

### Proof of Ownership

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

export default function ProofOfOwnership() {
  const { user } = useMoonKey();
  const { signMessage } = useSignMessage();
  const [proof, setProof] = useState<{ address: string; signature: string } | null>(null);

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

    const timestamp = Date.now();
    const proofMessage = `
I am the owner of this Solana wallet.

Address: ${user.wallet.address}
Timestamp: ${timestamp}
Challenge: ${crypto.randomUUID()}
    `.trim();

    try {
      const result = await signMessage({
        message: new TextEncoder().encode(proofMessage),
        wallet: user.wallet
      }, {
        uiConfig: {
          title: 'Prove Ownership',
          description: 'Sign to prove you own this Solana wallet',
          confirmButtonText: 'Generate Proof'
        }
      });

      const signature = bs58.encode(result.signature);
      
      setProof({
        address: user.wallet.address,
        signature
      });

      console.log('Proof generated:', { address: user.wallet.address, signature });
    } catch (error) {
      console.error('Proof generation failed:', error);
    }
  };

  return (
    <div className="proof-of-ownership">
      <h2>Proof of Wallet Ownership</h2>
      
      <button onClick={generateProof}>
        Generate Proof
      </button>

      {proof && (
        <div className="proof-result">
          <h3>Ownership Proof</h3>
          <div className="proof-details">
            <p><strong>Address:</strong></p>
            <code>{proof.address}</code>
            
            <p><strong>Signature:</strong></p>
            <code className="signature">{proof.signature}</code>
          </div>
          
          <button onClick={() => {
            navigator.clipboard.writeText(JSON.stringify(proof, null, 2));
            alert('Proof copied to clipboard!');
          }}>
            Copy Proof
          </button>
        </div>
      )}
    </div>
  );
}
```

## Verifying Signatures

### Client-side Verification

```tsx theme={null}
import { PublicKey } from '@solana/web3.js';
import nacl from 'tweetnacl';
import bs58 from 'bs58';

async function verifySolanaSignature(
  message: string,
  signature: string,
  publicKey: string
): Promise<boolean> {
  try {
    // Convert message to Uint8Array
    const messageBytes = new TextEncoder().encode(message);
    
    // Convert signature from base58 to Uint8Array
    const signatureBytes = bs58.decode(signature);
    
    // Convert public key to Uint8Array
    const publicKeyBytes = new PublicKey(publicKey).toBytes();
    
    // Verify signature
    const isValid = nacl.sign.detached.verify(
      messageBytes,
      signatureBytes,
      publicKeyBytes
    );
    
    return isValid;
  } catch (error) {
    console.error('Verification failed:', error);
    return false;
  }
}

// Usage
const isValid = await verifySolanaSignature(
  'Hello, Solana!',
  signature,
  walletAddress
);

console.log('Signature valid:', isValid);
```

### Install Dependencies

```bash theme={null}
npm install @solana/web3.js tweetnacl bs58
```

## UI Customization

Customize the signing modal:

```tsx theme={null}
await signMessage({
  message: new TextEncoder().encode('Sign this message'),
  wallet: user.wallet
}, {
  uiConfig: {
    title: 'Sign Message',
    description: 'Please sign this message to continue',
    confirmButtonText: 'Sign',
    cancelButtonText: 'Cancel'
  }
});
```

### Hide Modal

```tsx theme={null}
await signMessage({
  message: new TextEncoder().encode('Background signature'),
  wallet: user.wallet
}, {
  uiConfig: {
    showWalletUI: false
  }
});
```

<Warning>
  Hiding the 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>

## Best Practices

<AccordionGroup>
  <Accordion title="Include context in messages">
    Always provide clear context about what the user is signing:

    ```tsx theme={null}
    // ✅ Good - Clear and informative
    const message = `
    Welcome to My dApp!

    By signing this message, you verify ownership of your wallet.

    Wallet: ${address}
    Nonce: ${nonce}
    Timestamp: ${timestamp}
    `.trim();

    // ❌ Bad - Vague and unclear
    const message = 'Sign this';
    ```
  </Accordion>

  <Accordion title="Use nonces to prevent replay attacks">
    Include a unique nonce in each message:

    ```tsx theme={null}
    const nonce = crypto.randomUUID();
    const message = `
    Action: Login
    Nonce: ${nonce}
    Timestamp: ${Date.now()}
    `.trim();

    // Verify nonce hasn't been used before
    await verifyNonce(nonce);
    ```
  </Accordion>

  <Accordion title="Add timestamps for expiration">
    Include timestamps to make signatures time-limited:

    ```tsx theme={null}
    const timestamp = Date.now();
    const message = `
    Sign to authenticate
    Timestamp: ${timestamp}
    Valid for: 5 minutes
    `.trim();

    // On backend, check if signature is within valid time window
    const fiveMinutes = 5 * 60 * 1000;
    if (Date.now() - timestamp > fiveMinutes) {
      throw new Error('Signature expired');
    }
    ```
  </Accordion>

  <Accordion title="Store message with signature">
    Keep the original message to verify later:

    ```tsx theme={null}
    const result = await signMessage({
      message: new TextEncoder().encode(messageText),
      wallet: user.wallet
    });

    // Store both for verification
    const proof = {
      message: messageText,
      signature: bs58.encode(result.signature),
      address: user.wallet.address,
      timestamp: Date.now()
    };

    await saveToDatabase(proof);
    ```
  </Accordion>

  <Accordion title="Handle errors gracefully">
    Provide clear error messages:

    ```tsx theme={null}
    const { signMessage } = useSignMessage({
      onError: (error: any) => {
        if (error.code === 'USER_REJECTED') {
          console.log('User cancelled signing');
          return;
        } else if (error.message?.includes('wallet')) {
          alert('Wallet error. Please check your connection.');
        } else {
          alert('Failed to sign message: ' + error.message);
        }
      }
    });
    ```
  </Accordion>
</AccordionGroup>

## Troubleshooting

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

    * **Message mismatch**: Ensure exact same message is used for verification
    * **Encoding issues**: Use consistent encoding (TextEncoder for strings)
    * **Signature format**: Ensure signature is properly base58 encoded/decoded
    * **Public key format**: Verify public key is valid Solana address

    ```tsx theme={null}
    // Ensure exact message match
    const originalMessage = 'Hello, Solana!';
    const messageBytes = new TextEncoder().encode(originalMessage);

    // Sign
    const result = await signMessage({
      message: messageBytes,
      wallet: user.wallet
    });

    // Verify with SAME original message
    const isValid = await verifySolanaSignature(
      originalMessage, // Use original, not messageBytes
      bs58.encode(result.signature),
      user.wallet.address
    );
    ```
  </Accordion>

  <Accordion title="TextEncoder not defined">
    In Node.js environments, you may need to polyfill:

    ```tsx theme={null}
    // For Node.js < 11
    import { TextEncoder } from 'util';

    // Or use buffer
    const messageBytes = Buffer.from('Hello, Solana!', 'utf-8');
    ```
  </Accordion>

  <Accordion title="bs58 import errors">
    Ensure bs58 is properly installed:

    ```bash theme={null}
    npm install bs58
    npm install --save-dev @types/bs58
    ```

    ```tsx theme={null}
    // CommonJS
    const bs58 = require('bs58');

    // ES6
    import bs58 from 'bs58';
    ```
  </Accordion>

  <Accordion title="Callback not firing">
    Configure callbacks on hook initialization:

    ```tsx theme={null}
    // ✅ Correct
    const { signMessage } = useSignMessage({
      onSuccess: ({ signature }) => {
        console.log('Success:', bs58.encode(signature));
      },
      onError: (error) => {
        console.log('Error:', error);
      }
    });

    // Then call it
    await signMessage({ message, wallet });
    ```
  </Accordion>
</AccordionGroup>

## Related Documentation

<CardGroup cols={2}>
  <Card title="Sign Transaction" icon="pen-to-square" href="/wallet-as-a-service/using-wallets/solana/sign-transaction">
    Sign Solana transactions
  </Card>

  <Card title="Send Transaction" icon="paper-plane" href="/wallet-as-a-service/using-wallets/solana/send-transaction">
    Send transactions to the Solana network
  </Card>

  <Card title="Solana Web3.js" icon="code" href="https://solana-labs.github.io/solana-web3.js/">
    Official Solana JavaScript library
  </Card>

  <Card title="TweetNaCl" icon="lock" href="https://github.com/dchest/tweetnacl-js">
    Cryptography library for verification
  </Card>
</CardGroup>

## Next Steps

* Learn about [Solana transaction signing](/wallet-as-a-service/using-wallets/solana/sign-transaction)
* Understand [Solana's signature verification](https://docs.solana.com/developing/clients/javascript-api#signature-verification)
* Explore [Sign-In With Solana (SIWS)](https://github.com/phantom/sign-in-with-solana) standard
* Implement [session-based authentication](/authentication/user-authentication/authentication-state) with signed messages
