| name | sign-message |
| description | Implement message signing with Phantom Connect SDK for Solana and EVM, including Sign-in with Solana (SIWS) authentication |
Sign Message
When to use
- User asks to sign a message
- User wants to implement SIWS (Sign-in with Solana)
- User wants to verify wallet ownership or add wallet-based authentication
Workflow
Step 1: Sign a Message on Solana (React SDK)
import { useSolana } from "@phantom/react-sdk";
function SignMessage() {
const solana = useSolana();
const handleSign = async () => {
const message = new TextEncoder().encode("Hello from my dApp!");
try {
const { signature } = await solana.signMessage(message);
console.log("Signature:", signature);
} catch (error) {
console.error("Signing failed:", error);
}
};
return <button onClick={handleSign}>Sign Message</button>;
}
Step 2: Sign a Message on Solana (Browser SDK)
async function signMessage(sdk: BrowserSDK) {
const message = new TextEncoder().encode("Hello from my dApp!");
try {
const { signature } = await sdk.solana.signMessage(message);
console.log("Signature:", signature);
} catch (error) {
console.error("Signing failed:", error);
}
}
Step 3: Sign a Message on EVM
For Ethereum/EVM chains, use signPersonalMessage:
import { useEthereum } from "@phantom/react-sdk";
function SignEVMMessage() {
const ethereum = useEthereum();
const handleSign = async () => {
try {
const { signature } = await ethereum.signPersonalMessage("Hello from my dApp!");
console.log("Signature:", signature);
} catch (error) {
console.error("Signing failed:", error);
}
};
return <button onClick={handleSign}>Sign EVM Message</button>;
}
Step 4: Sign-in with Solana (SIWS)
SIWS provides a standardized authentication flow — proving wallet ownership to your backend:
import { useSolana, useAccounts } from "@phantom/react-sdk";
function SignInWithSolana() {
const solana = useSolana();
const { accounts } = useAccounts();
const handleSIWS = async () => {
const solanaAccount = accounts.find((a) => a.chain === "solana");
if (!solanaAccount) return;
const domain = window.location.host;
const uri = window.location.origin;
const nonce = await fetch("/api/auth/nonce").then((r) => r.text());
const issuedAt = new Date().toISOString();
const message = [
`${domain} wants you to sign in with your Solana account:`,
solanaAccount.address,
"",
"Sign in to access your account.",
"",
`URI: ${uri}`,
`Version: 1`,
`Nonce: ${nonce}`,
`Issued At: ${issuedAt}`,
].join("\n");
try {
const { signature } = await solana.signMessage(
new TextEncoder().encode(message)
);
const response = await fetch("/api/auth/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message,
signature,
publicKey: solanaAccount.address,
}),
});
if (response.ok) {
console.log("Authenticated successfully!");
}
} catch (error) {
console.error("SIWS failed:", error);
}
};
return <button onClick={handleSIWS}>Sign in with Solana</button>;
}
API Reference
| Chain | Method | Input | Output |
|---|
| Solana | solana.signMessage(bytes) | Uint8Array | { signature } |
| Ethereum | ethereum.signPersonalMessage(msg) | string | { signature } |
Important Notes
- Solana
signMessage expects a Uint8Array — use new TextEncoder().encode(...) to convert strings.
- EVM
signPersonalMessage accepts a plain string.
- For SIWS, the nonce must be issued by your backend, stored server-side, and validated as single-use on verification. A client-generated nonce provides no replay protection.
- Always wrap signing calls in try-catch to handle user rejections.