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

# Sign Message

> Sign a message using an action code

export const SignMessage = () => {
  const [message, setMessage] = useState("");
  const [status, setStatus] = useState(null);
  const [loading, setLoading] = useState(false);
  const [code, setCode] = useState("");
  const [observing, setObserving] = useState(false);
  const [signedMessage, setSignedMessage] = useState(null);
  const [pubkey, setPubkey] = useState(null);
  const [error, setError] = useState(null);
  const truncatePubkey = key => key ? `${key.slice(0, 5)}...${key.slice(-5)}` : "";
  const resolveCode = async actionCode => {
    const response = await fetch(`https://relay.actioncodes.org/resolve/solana/${actionCode}`, {
      method: "GET",
      headers: {
        "Authorization": "Bearer kfTLXFBGzWdqhqZVKD5P0EBJbWoAXlAEfhZguphX9aI"
      }
    });
    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(error.message || "Failed to resolve code");
    }
    return response.json();
  };
  const consumeCode = async (actionCode, messageText) => {
    const response = await fetch("https://relay.actioncodes.org/consume", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer kfTLXFBGzWdqhqZVKD5P0EBJbWoAXlAEfhZguphX9aI"
      },
      body: JSON.stringify({
        code: actionCode,
        chain: "solana",
        payload: {
          mode: "sign-only-message",
          message: messageText
        }
      })
    });
    if (!response.ok) {
      const errorText = await response.text();
      let errorMessage = "Failed to consume code";
      try {
        const err = JSON.parse(errorText);
        errorMessage = err.message || errorMessage;
      } catch {}
      throw new Error(errorMessage);
    }
  };
  const observeSignature = async actionCode => {
    const maxAttempts = 60;
    const interval = 2000;
    for (let i = 0; i < maxAttempts; i++) {
      try {
        const data = await resolveCode(actionCode);
        if (data.data?.signedMessage) {
          return {
            signedMessage: data.data.signedMessage,
            pubkey: data.pubkey
          };
        }
      } catch {}
      await new Promise(r => setTimeout(r, interval));
    }
    throw new Error("Timed out waiting for signature");
  };
  const handleSubmit = async e => {
    e.preventDefault();
    setLoading(true);
    setStatus(null);
    setSignedMessage(null);
    setPubkey(null);
    setError(null);
    setObserving(false);
    if (!(/^\d{8}$/).test(code)) {
      setStatus("invalid_code");
      setLoading(false);
      return;
    }
    try {
      await resolveCode(code);
      await consumeCode(code, message);
      setLoading(false);
      setStatus("waiting");
      setObserving(true);
      const result = await observeSignature(code);
      setSignedMessage(result.signedMessage);
      setPubkey(result.pubkey);
      setStatus("signed");
    } catch (err) {
      setStatus("error");
      setError(err.message);
    } finally {
      setLoading(false);
      setObserving(false);
    }
  };
  return <>
      <form onSubmit={handleSubmit} className="flex flex-col items-center gap-4 w-full max-w-xl mx-auto">
        <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 w-full">
          <input type="text" value={message} onChange={e => setMessage(e.target.value)} placeholder="Enter message" className="border rounded px-3 py-2 w-full min-w-0" disabled={loading || observing} />
          <input type="text" value={code} onChange={e => setCode(e.target.value)} placeholder="8-digit code" className="border rounded px-3 py-2 w-full sm:w-40 min-w-0" disabled={loading || observing} />
          <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50 w-full sm:w-auto" disabled={loading || observing || !message || !code}>
            {loading ? "Submitting..." : "Submit"}
          </button>
        </div>
        <div className="w-full">
          {status === "waiting" && <div className="flex items-center gap-2 text-yellow-600">
              <svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
              </svg>
              Check your wallet to sign the message
            </div>}
          {status === "signed" && signedMessage && <div className="bg-zinc-100 dark:bg-zinc-800 p-3 rounded text-sm overflow-x-auto">
              <div className="text-green-600 mb-2">✓ Message signed</div>
              {pubkey && <div className="mb-2"><span className="font-semibold">Wallet:</span> {truncatePubkey(pubkey)}</div>}
              <span className="font-semibold">Signed Message:</span>
              <div className="flex flex-col sm:flex-row items-start sm:items-center gap-2 mt-1">
                <code className="bg-white dark:bg-zinc-900 px-2 py-1 rounded text-xs break-all w-full">{signedMessage}</code>
                <button type="button" className="text-blue-600 underline text-xs" onClick={() => navigator.clipboard.writeText(signedMessage)}>
                  Copy
                </button>
              </div>
            </div>}
          {status === "invalid_code" && <div className="text-red-600">Code must be 8 digits.</div>}
          {status === "error" && <div className="text-red-600">{error || "Failed to sign message."}</div>}
        </div>
      </form>
    </>;
};

This demo shows how message signing works with Action Codes. You'll attach a message to your code and sign it with your wallet.

<Note>
  This only demonstrates the signing flow. No transactions are sent — you're just signing a message.
</Note>

## How to use this demo

<Steps>
  <Step title="Get a code from actioncode.app">
    Open [actioncode.app](https://actioncode.app) in your Solana wallet's browser (Phantom, Solflare, etc.), connect your wallet, and tap "Get Code"
  </Step>

  <Step title="Enter your code and message below">
    Paste your 8-digit code and type the message you want to sign
  </Step>

  <Step title="Approve in actioncode.app">
    Go back to actioncode.app — you'll see the signing request. Approve it with your wallet.
  </Step>

  <Step title="See the result">
    Once approved, the signed message appears below
  </Step>
</Steps>

<Warning>
  Codes expire in \~2 minutes. Get a fresh code right before using this demo.
</Warning>

***

<SignMessage />

***

## What's happening

This demo calls the Action Codes SDK:

```typescript theme={null}
// 1. Resolve the code to verify it
const actionCode = await client.resolve(code)

// 2. Attach the message
await client.attachMessage(code, message)

// 3. Wait for user approval
for await (const status of client.observeStatus(code)) {
  if (status.signedMessage) {
    // User signed the message!
  }
}
```

For the full SDK reference, see [SDK Methods](/sdk/methods).
