# Signature

**Endpoint:** `/action/signature`

Asks the currently logged-in WalletTwo user to sign an arbitrary text message using their wallet's private key. The resulting cryptographic signature is returned to the host page via `postMessage` and optionally via redirect.

Use this when you need to prove that a specific WalletTwo user controls a given wallet address, or when you need a signed payload for your own on-chain or off-chain logic.

***

### Required user state

| Check             | Middleware                | Redirects to if missing |
| ----------------- | ------------------------- | ----------------------- |
| User is logged in | `LoggedMiddleware`        | `/auth/login`           |
| Email is verified | `EmailVerifiedMiddleware` | `/auth/email/verify`    |
| Wallet is created | `WalletMiddleware`        | `/auth/wallet/register` |

***

### Iframe URL

```
https://<WALLETTWO_ORIGIN>/action/signature?iframe=true&message=<ENCODED_MESSAGE>
```

Parameters:

| Param          | Required            | Type                    | Description                                       |
| -------------- | ------------------- | ----------------------- | ------------------------------------------------- |
| `message`      | Yes                 | `string`                | The raw text message to sign. Must be URL-encoded |
| `redirect_uri` | No                  | `string` (absolute URL) | If provided, iframe navigates here after signing  |
| `iframe`       | Yes (for embedding) | `"true"`                | Activates bare rendering mode                     |

Full example:

```
https://<WALLETTWO_ORIGIN>/action/signature?iframe=true&message=Please%20verify%20your%20identity&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
```

If `message` is not supplied, the action renders the signing UI but nothing happens (no signing, no events).

***

### What happens when the action runs

1. On mount, the action calls `wallet.signMessage(message)`.
2. While signing is in progress, a spinner state (`isSigning`) is shown to the user.
3. On success:
   * `window.parent.postMessage(...)` is fired.
   * The success state (`isSigned`) is shown.
   * If `redirect_uri` is present, a 3-second countdown starts before redirecting.
4. The message preview (truncated to 100 characters) is always shown in the UI so the user knows what they are signing.

***

### postMessage event

```js
{
  event: "message_signed",
  message: "<original-message>",
  signature: "<hex-signature>",
  user: "<wallettwo-user-id>",
  wallet: "<wallet-address>"
}
```

| Field       | Description                                     |
| ----------- | ----------------------------------------------- |
| `event`     | Always `"message_signed"`                       |
| `message`   | The original plain-text message that was signed |
| `signature` | The EIP-191 personal signature (hex string)     |
| `user`      | WalletTwo internal user ID                      |
| `wallet`    | The wallet address that produced the signature  |

***

### redirect\_uri callback

After the 3-second countdown, the iframe navigates to:

```
<redirect_uri>?signature=<hex>&usr=<user-id>&wlt=<wallet-address>
```

| Param       | Description       |
| ----------- | ----------------- |
| `signature` | The hex signature |
| `usr`       | WalletTwo user ID |
| `wlt`       | Wallet address    |

***

### Verifying the signature on your backend

The signature is a standard EIP-191 personal sign. Verify it with any library that supports `eth_sign` / `personal_sign`:

```js
// Node.js example using ethers v6
import { ethers } from "ethers";

function verifySignature(message, signature, expectedWallet) {
  const recovered = ethers.verifyMessage(message, signature);
  return recovered.toLowerCase() === expectedWallet.toLowerCase();
}
```

The `message` you verify against must be the exact string you passed in the URL — no extra encoding or modification.

***

### Host page integration

```html
<iframe
  id="w2-signature"
  src="https://<WALLETTWO_ORIGIN>/action/signature?iframe=true&message=Verify%20my%20identity%20for%20example.com"
  style="width:420px;height:700px;border:0;"
></iframe>

<script>
  const WALLET_TWO_ORIGIN = "https://<WALLETTWO_ORIGIN>";

  window.addEventListener("message", (event) => {
    if (event.origin !== WALLET_TWO_ORIGIN) return;

    if (event.data?.event === "message_signed") {
      const { signature, message, wallet, user } = event.data;
      // Verify on your backend
      verifyOnBackend(message, signature, wallet, user);
    }
  });
</script>
```

***

### UI states inside the iframe

| State               | What the user sees                                                        |
| ------------------- | ------------------------------------------------------------------------- |
| Signing in progress | Spinning border animation + "Signing Message" heading                     |
| Signed successfully | Green checkmark + "Message Signed!" + countdown if `redirect_uri` present |
| No `message` param  | Signing state mounts but no action is triggered                           |

The iframe always shows a preview of the message (first 100 chars + `...` if longer).

***

### Security checklist

* Always verify `event.origin` before trusting the event.
* Always verify the signature server-side against the original message and the expected wallet address.
* Do not rely solely on the `wallet` field returned in the event — confirm it matches by recovering the signer from the signature itself.
* Be specific about what can be signed: restrict the `message` parameter to the exact challenge string your backend issues. Avoid accepting arbitrary user-controlled messages.
