apps/web) signs everything with a connected Privy embedded wallet because that’s the lowest-friction path for trying Leash from a browser. The SDK itself has no Privy dependency — @leash/registry-utils only cares that umi.identity can sign Solana transactions. This guide shows the three production-grade alternatives.
Pick a signer style
| Style | Custody | Recovery | Best for |
|---|---|---|---|
| Privy embedded wallet | Privy holds an encrypted shard | Email / OAuth | Browser-first agents with human users |
BYO local keypair (solana-keygen JSON) | You hold the 64-byte secret | None — back it up yourself | Headless scripts, CI, “I just want to play without an account” |
Server-side env var (LEASH_DEV_PAYER_SECRET_KEY) | The Node process holds it in memory | Whatever your secret manager provides | Cron jobs, internal tools, the playground’s headless POST routes |
| KMS / TEE / hardware wallet | A managed signer service | Provider-specific | Production agents managing real money |
keypairIdentity plugin, then call @leash/registry-utils exactly the same way. Only the source of bytes differs.
BYO local keypair
Generate one with the Solana CLI (solana-keygen new -o agent-owner.json) or with the SDK’s portable helper:
generateOperatorKeypair produces a Solana-compatible 64-byte secret (secret(32) || public(32)) — byte-identical to what solana-keygen writes — so any tool in the Solana ecosystem can read it. There is no Solana RPC dependency, so the same call works in Node, the browser, and edge runtimes.
Fund the new pubkey with a tiny bit of SOL on whichever cluster you’re targeting (solana airdrop 1 <pubkey> --url devnet) before continuing — the mint, ATA provisioning, and delegation steps all need rent.
Then mint an agent with that keypair as umi.identity:
agent-owner.json is now the owner — they alone can sign withdrawals, change delegations, set the agent token, etc. Lose the file, lose the agent. Back it up.
Server-side env var
The headless server routes (POST /api/agents/create, POST /api/agents/executive) and any custom Node service can use this pattern. Drop a base58 (or JSON-array) secret key into LEASH_DEV_PAYER_SECRET_KEY and the playground’s getServerUmi() will load it as umi.identity:
Splitting owner and executive
For production agents we strongly recommend two different keypairs — see Identities for why. The setup is twokeypairIdentity swaps in sequence:
Generating an operator keypair too
The owner / executive split above covers funds. The operator is a separate concern — the agent’s own off-chain signing identity. You generate it the same way and optionally advertise it on-chain inside the agent’s metadata:operator.json at boot, signs receipts and attestations with signWithOperator(operator.secretKey, message), and any peer can verify the binding by reading the on-chain AgentMetadata.registrations and matching the advertised pubkey.
Two-step signing (hardware wallets, priority fees, custom retries)
Whenumi.identity is a hardware wallet or you need to add priority fees / custom retries, swap the one-shot helpers for their prepare* siblings:
@leash/registry-utils ships a matching prepare* function that returns an unsigned TransactionBuilder plus echo fields, so you can sign and submit on your own terms. See the Prepare/Send split section in the SDK reference for the full list — this is the same surface the upcoming HTTP API speaks.
See also
- Identities — when each keypair is involved and what it signs.
- Create an agent — full walkthrough of the mint flow.
- Fund an agent — per-mint SPL allowances for the executive.

