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:
Your app calls the sign message method from the MoonKey SDK
The Sign Message screen appears, displaying the message content
User reviews the message details
User approves or rejects the signature request
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:
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
Custom title text displayed at the top of the Sign Message screen. Example: options : {
uiOptions : {
title : 'Verify Your Identity'
}
}
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'
}
}
Custom text for the confirmation button. Default: 'Sign'Example: options : {
uiOptions : {
buttonText : 'Approve & Sign'
}
}
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
The commitment level to use for Solana transactions. Available values: 'processed', 'confirmed', 'finalized'Example: options : {
uiOptions : {
commitment : 'confirmed'
}
}
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\n Timestamp: ${ 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\n Nonce: ${ 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\n Details: ${ details } \n\n Timestamp: ${ 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\n Version: 2.0 \n Date: ${ 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\n Wallet: ${ user . wallet . address } \n Timestamp: ${ Date . now () } \n\n Sign 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
Trigger signature request
Your application calls signMessage() with a message, wallet, and optional UI options.
Sign Message screen appears
The customized Sign Message screen is displayed to the user.
Review message
User reviews the message content, title, and description.
Approve or reject
User clicks the confirm button to sign, or cancels/closes the modal to reject.
Signature created
If approved, MoonKey signs the message with the user’s wallet private key.
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:
Message Signing (this screen)
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.'
}
}
Include timestamps or nonces
Prevent replay attacks by including unique data: message : `Action: Login \n Timestamp: ${ Date . now () } \n Nonce: ${ randomNonce } `
Use descriptive button text
Make it clear what the user is signing: options : {
uiOptions : {
buttonText : 'Sign & Verify' // Better than just "Sign"
}
}
Handle rejections gracefully
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' );
}
Show wallet details when relevant
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
Check for wallet before signing
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:
Verify signatures server-side - Always validate signatures on your backend before trusting them
Use nonces - Include unique identifiers to prevent replay attacks
Include context - Clearly state what the signature authorizes
Set expiration - Include timestamps and reject old signatures
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:
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