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

# Server Signer

> Authorize wallet operations directly from your server, using an API key and secret.

export const SignerSecretGenerator = () => {
  const [secret, setSecret] = useState("");
  const [copied, setCopied] = useState(false);
  const bytesToHex = bytes => {
    return Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
  };
  const generate = () => {
    const bytes = crypto.getRandomValues(new Uint8Array(32));
    const hex = bytesToHex(bytes);
    setSecret(`xmsk1_${hex}`);
  };
  const envLine = secret ? `CROSSMINT_SIGNER_SECRET="${secret}"` : "";
  const handleCopy = async text => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch (_err) {}
  };
  return <div className="not-prose my-8 rounded-xl border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 p-6 shadow-lg">
            {}
            <div className="mb-6 flex items-center justify-between gap-2 flex-wrap">
                <div className="flex items-center gap-3">
                    <svg width="28" height="28" viewBox="0 0 100 100" fill="none">
                        <defs>
                            <linearGradient id="markGrad" x1="0%" y1="0%" x2="100%" y2="100%">
                                <stop offset="0%" stopColor="#5EDD4D" />
                                <stop offset="100%" stopColor="#05CE6C" />
                            </linearGradient>
                        </defs>
                        <path d="M50 0C50 27.6 27.6 50 0 50C27.6 50 50 72.4 50 100C50 72.4 72.4 50 100 50C72.4 50 50 27.6 50 0Z" fill="url(#markGrad)" />
                    </svg>
                    <div>
                        <h3 className="text-lg font-semibold text-zinc-900 dark:text-zinc-100 m-0">
                            Signer Secret Generator
                        </h3>
                        <p className="text-xs text-zinc-500 dark:text-zinc-400 m-0 mt-0.5">
                            Generate a master secret for your signing environment
                        </p>
                    </div>
                </div>
                <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 text-green-700 dark:text-green-400 text-xs font-medium">
                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                        <rect x="3" y="11" width="18" height="11" rx="2" />
                        <path d="M7 11V7a5 5 0 0110 0v4" />
                    </svg>
                    Client-side only
                </span>
            </div>

            {}
            <div className="mb-5">
                <div className="rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 p-3">
                    <div className="flex gap-2">
                        <input type="text" value={secret} onChange={e => setSecret(e.target.value)} placeholder="xmsk1_a3f8...c7b2" spellCheck={false} className="flex-1 min-w-0 px-3 py-2 text-sm font-mono bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-600 rounded-lg text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-green-500" />
                        <button type="button" onClick={generate} className="inline-flex items-center gap-1.5 px-4 py-2 bg-green-500 hover:bg-green-600 text-white text-sm font-semibold rounded-lg transition-colors whitespace-nowrap">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                <polyline points="23 4 23 10 17 10" />
                                <path d="M20.49 15a9 9 0 11-2.12-9.36L23 10" />
                            </svg>
                            Generate
                        </button>
                    </div>
                </div>
            </div>

            {}
            {secret && <div className="mb-5">
                    <div className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 mb-2">
                        Your environment variable
                    </div>
                    <div className="flex items-center gap-2 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-3 py-2.5">
                        <code className="flex-1 min-w-0 text-xs font-mono text-zinc-700 dark:text-zinc-300 overflow-hidden text-ellipsis whitespace-nowrap">
                            {envLine}
                        </code>
                        <button type="button" onClick={() => handleCopy(envLine)} className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors whitespace-nowrap ${copied ? "bg-green-100 dark:bg-green-900/30 border border-green-300 dark:border-green-700 text-green-700 dark:text-green-400" : "bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-900/40"}`}>
                            {copied ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                    <polyline points="20 6 9 17 4 12" />
                                </svg> : <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                    <rect x="9" y="9" width="13" height="13" rx="2" />
                                    <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
                                </svg>}
                            {copied ? "Copied!" : "Copy"}
                        </button>
                    </div>
                </div>}
        </div>;
};

<Warning>
  **You are viewing docs for the previous version of the Wallets SDK.** We recommend upgrading to V1.
  See the [updated version of this page](/wallets/guides/signers/server-signer) or the [V1 migration guide](/wallets/guides/migrate-to-v1).
</Warning>

Authorize transactions directly from a server environment using a Crossmint API key, and a secret provided by you. Signing happens entirely in-process with no external network call, making this the lowest-latency option available.

Because this key lives as a secret in your infrastructure, enforcing regular rotations is recommended — for example, quarterly — to limit exposure in the event of a compromised environment, a departed employee, or an infrastructure incident.

For a conceptual overview, see [Server signer](/wallets/v0/concepts/wallet-signers#server-signer) in the Wallet Signers guide. To learn how to register additional operational signers on an existing wallet, see [Registering a signer](/wallets/v0/guides/signers/registering-a-signer).

## Configuration

<Steps>
  <Step title="Generate a Signer Secret">
    You must generate a signer secret to ensure that no one — including Crossmint — can access or control your wallet. This secret:

    1. Must be either 64 hex characters (case-insensitive) or use the prefixed format `xmsk1_<64-hex-chars>`.
    2. Remains on your server and is never transmitted to Crossmint. The SDK derives a private/public keypair from it locally and uses public-key infrastructure (PKI) for authorization — only the public key is ever shared.

    <Warning>
      Generate and store this secret securely. This secret can grant full signing authority over the wallet.
    </Warning>

    Generate a secret using the [tool below](#hkdf-sha256-key-derivation-tool) or [programmatically](#generate-your-own-key-programmatically), and store it as the `CROSSMINT_SIGNER_SECRET` environment variable on your server:

    ```bash theme={null}
    CROSSMINT_SIGNER_SECRET="xmsk1_<your-64-hex-character-secret>"
    # OR
    CROSSMINT_SIGNER_SECRET="<your-64-hex-character-secret>"

    # EXAMPLE
    CROSSMINT_SIGNER_SECRET="b62b473b3ba7d3d72bf7c3a397f1eb61c305a4193ce670dd056686c13d95bbaa"
    ```

    The Crossmint SDK automatically derives chain-specific signing keys from this secret, scoped to your **project ID**, **environment**, and target **chain**. A single secret can be safely reused across chains within a project.
  </Step>

  <Step title="Create a Wallet with a Server Signer">
    Pass `type: "server"` as the signer type when creating a wallet. The SDK handles key derivation and address resolution automatically.

    <Tabs>
      <Tab title="Node.js">
        ```typescript theme={null}
        import { CrossmintWallets, createCrossmint } from "@crossmint/wallets-sdk";

        const crossmint = createCrossmint({
            apiKey: "<your-server-api-key>",
        });
        const crossmintWallets = CrossmintWallets.from(crossmint);

        const wallet = await crossmintWallets.createWallet({
            chain: "base-sepolia",
            signer: {
                type: "server",
                secret: process.env.CROSSMINT_SIGNER_SECRET,
            },
            alias: "my-server-wallet"
        });
        ```

        <Tip>
          The `alias` parameter lets you retrieve the wallet later without storing the wallet address:

          ```typescript theme={null}
          const wallet = await crossmintWallets.getWallet(
              "evm:alias:my-server-wallet",
              { signer: { type: "server", secret: process.env.CROSSMINT_SIGNER_SECRET } }
          );
          ```
        </Tip>
      </Tab>

      <Tab title="REST">
        Server wallets require using the Crossmint SDK because the SDK handles key derivation and transaction signing locally. If you need to work with the REST API directly, use an [external wallet signer](/wallets/v0/guides/signers/external-wallet) instead — it provides equivalent functionality and lets you manage your own keys while calling the API without the SDK.
      </Tab>
    </Tabs>
  </Step>
</Steps>

## HKDF-SHA256 Key Derivation Tool

Use this tool to generate a master signer secret for your server-side or agent wallet environment. The secret is generated entirely in your browser — nothing is transmitted to any server.

<SignerSecretGenerator />

### Generate Your Own Key Programmatically

You can also generate a signer secret programmatically:

<CodeGroup>
  ```typescript Node.js / TypeScript theme={null}
  import { randomBytes } from "crypto";

  function generateSignerSecret(): string {
    const bytes = randomBytes(32);
    const hex = bytes.toString("hex");
    return `xmsk1_${hex}`;
  }

  const secret = generateSignerSecret();
  console.log(`CROSSMINT_SIGNER_SECRET="${secret}"`);
  ```

  ```python Python theme={null}
  import secrets

  def generate_signer_secret() -> str:
      hex_bytes = secrets.token_hex(32)
      return f"xmsk1_{hex_bytes}"

  secret = generate_signer_secret()
  print(f'CROSSMINT_SIGNER_SECRET="{secret}"')
  ```

  ```bash Shell (openssl) theme={null}
  echo "CROSSMINT_SIGNER_SECRET=\"xmsk1_$(openssl rand -hex 32)\""
  ```
</CodeGroup>

## Under the Hood

A server signer is, at its core, a secret that gets deterministically mapped into a private key compatible with the underlying blockchain network. You provide a master secret, and the Crossmint SDK derives chain-specific signing keys from it — so a single secret works across all supported chains within a project.

### How Key Derivation Works

A 32-byte (256-bit) random value serves as your master secret. The SDK derives chain-specific private keys from this master secret using **HKDF-SHA256**, scoped to your project ID, environment, and target chain:

```text theme={null}
key = HKDF-SHA256(
  ikm:  <master secret>,
  salt: "crossmint",
  info: "<projectId>:<environment>:<chain>-<algorithm>"
)
```

The `projectId` and `environment` are extracted automatically from your server API key by the SDK — the same master secret produces entirely different signing keys per project, environment, and chain. This means a single secret can be safely reused across chains within a single project.

You can derive the public address using Crossmint's wallets SDK — see the <a href="https://github.com/Crossmint/crossmint-sdk/blob/main/packages/wallets/src/signers/server/helpers/derive-server-signer.ts" target="_blank">derive-server-signer helper</a> for reference.

### How Are Server Signer Locators Derived

When referencing a server signer in API calls (for example, when submitting approvals or registering it as an operational signer), use the locator format `server:<address>`:

```text theme={null}
server:0x1234567890123456789012345678901234567890
```

The `<address>` is the public address derived from your signer secret for the target chain. The SDK computes this automatically when you use `type: "server"` — you only need the locator format when working with the REST API directly.
