Skip to main content

Wallet-as-Identity

Aether MCP uses Wallet-as-Identity authentication: your Solana wallet IS your identity. No usernames, no passwords — just cryptographic signatures.

Why Wallet-as-Identity?

  • Cryptographically Secure: Ed25519 signatures cannot be forged
  • No Passwords: No risk of password leaks or brute force
  • One Identity: Your wallet address is your unique identifier
  • Permissionless: No registration required, authenticate instantly
  • Cross-Platform: Same identity across all MCP clients

Authentication Flows

Development Flow (All Networks)

Best for: Testing, experimentation, rapid prototyping wallet_create handles everything in a single call: generates a wallet, encrypts and stores it, auto-authenticates (challenge + sign + verify + JWT), and airdrops SOL on devnet.
1

Create and Authenticate

{
  "tool": "wallet_create",
  "params": {
    "airdropSol": 1,
    "label": "my-test-wallet"
  }
}
Returns a fully authenticated session:
{
  "success": true,
  "data": {
    "address": "8FE27iak4b2yadKoogAPAGN9VnmYYZm8eUF71QhVbgNr",
    "label": "my-test-wallet",
    "network": "devnet",
    "balances": { "sol": 1.0, "usdc": 0, "athr": 0 },
    "sessionToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "authenticated": true,
    "isNewSession": true
  }
}
One call to go from nothing to a fully authenticated, funded wallet. Ready to use all tools immediately.

Production Flow (Mainnet)

Best for: Production, user-owned wallets (Phantom, Solflare, etc.)
1

Get Challenge

{
  "tool": "session_auth",
  "params": {
    "walletAddress": "YOUR_WALLET_ADDRESS"
  }
}
Server generates a unique challenge:
{
  "success": true,
  "data": {
    "nonce": "a1b2c3d4e5f6...",
    "message": "Sign this message to authenticate with Aether MCP\n\nWallet: YOUR_WALLET_ADDRESS\nNonce: a1b2c3d4e5f6...\nTimestamp: 2025-02-06T10:15:30Z",
    "expiresAt": "2025-02-06T10:20:30Z"
  }
}
Challenge expires in 5 minutes for security.
2

Sign Challenge

Sign the message with your wallet’s private key.
const message = new TextEncoder().encode(challenge.data.message);
const signature = await window.solana.signMessage(message, 'utf8');
const signatureBase64 = btoa(String.fromCharCode(...signature.signature));
3

Submit Signature

{
  "tool": "wallet_connect",
  "params": {
    "walletAddress": "YOUR_WALLET_ADDRESS",
    "nonce": "a1b2c3d4e5f6...",
    "signature": "BASE64_SIGNATURE_HERE"
  }
}
Server verifies signature and creates session:
{
  "success": true,
  "data": {
    "walletAddress": "YOUR_WALLET_ADDRESS",
    "sessionToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "isNewSession": true,
    "expiresAt": "2025-02-13T10:15:30Z"
  }
}
Session token valid for 7 days by default.
wallet_connect is mainnet-only. On devnet, wallet_create handles authentication automatically.

Session Fingerprinting

The server creates a session fingerprint using SHA256(IP + User-Agent) to associate sessions with clients. This enables:
  • Automatic session persistence: Reconnecting from the same client restores your session
  • Wallet-to-fingerprint linking: The server remembers which wallet was used from which client
  • Keypair restoration on devnet: When reconnecting, the server can automatically restore your wallet keypair
Fingerprint = SHA256(client_ip + user_agent_header)
This means that if you reconnect from the same device/client, your previous wallet session can be reconstructed automatically without re-authentication.

Session Persistence

Configurable Timeout

Session timeout is configurable via the MCP_SESSION_TIMEOUT_MS environment variable:
SettingDefaultDescription
MCP_SESSION_TIMEOUT_MS3600000 (1 hour)Time before an inactive in-memory session is cleaned up
This controls in-memory session cleanup only. The session data persists in the database (SQLite/Redis) and can be reconstructed.

Automatic Session Reconstruction

When an in-memory session expires but the session data still exists in the database, the server automatically reconstructs the session:
  1. Client reconnects with the same fingerprint
  2. Server finds the fingerprint-wallet link in the database
  3. Session state is rebuilt from persisted data
  4. On devnet, the wallet keypair is restored from encrypted storage
  5. The session is fully functional again without re-authentication
This makes the experience seamless across MCP transport reconnections and server restarts.

Session Management

Session Tokens

JWT tokens are issued after successful authentication:
{
  "walletAddress": "8FE27iak...",
  "sessionId": "uuid-v4",
  "jti": "unique-token-id",
  "iat": 1707217730,
  "exp": 1707822530
}
Properties:
  • walletAddress: Your authenticated wallet
  • sessionId: Unique session identifier
  • jti: JWT ID for revocation
  • exp: Expiration timestamp (7 days)

Check Session Status

{
  "tool": "session_info"
}

Session Restoration

Restore your session from a JWT token:
{
  "tool": "session_restore",
  "params": {
    "token": "YOUR_JWT_TOKEN"
  }
}
Auto-refresh: If token expires in < 1 hour, a new token is returned.

Session Revocation

Revoke Current Token

Immediately invalidate your current session token:
{
  "tool": "wallet_disconnect"
}
Or use the HTTP endpoint:
curl -X POST https://mcp-devnet.getaether.xyz/auth/revoke \
  -H "Authorization: Bearer YOUR_TOKEN"

Revoke All Sessions

Revoke all sessions for your wallet (security emergency):
1

Get Challenge

{ "tool": "session_auth" }
2

Sign & Revoke

curl -X POST https://mcp-devnet.getaether.xyz/auth/revoke-all \
  -H "Content-Type: application/json" \
  -d '{
    "walletAddress": "YOUR_WALLET",
    "signature": "BASE64_SIGNATURE",
    "nonce": "CHALLENGE_NONCE"
  }'
Use Case: Compromised token, lost device, security breach.
After revocation, all existing tokens become invalid immediately. You’ll need to re-authenticate.

Security Best Practices

  • Use secure storage (OS keychain, encrypted storage)
  • Never commit tokens to git
  • Regenerate tokens if exposed
  • Don’t store in localStorage (XSS risk)
  • Don’t log tokens in production
The server verifies:
  1. Signature matches wallet’s public key (Ed25519)
  2. Challenge nonce exists and hasn’t expired
  3. Message content matches exactly
  4. Nonce used only once (replay protection)
  • Default: 7 days
  • Auto-refresh: When < 1 hour remaining
  • Revocation: Instant via JWT blacklist
  • Cleanup: Expired tokens removed hourly
On mainnet:
  • Signature verification extra strict
  • Rate limiting enforced (5 auth attempts / 15 min)
  • All transfers logged and monitored

HTTP Authentication

For direct HTTP/REST access (non-MCP clients):

Get Challenge

GET https://mcp-devnet.getaether.xyz/auth/challenge?wallet=YOUR_WALLET

Submit Signature

POST https://mcp-devnet.getaether.xyz/auth/verify
Content-Type: application/json

{
  "walletAddress": "YOUR_WALLET",
  "nonce": "CHALLENGE_NONCE",
  "signature": "BASE64_SIGNATURE"
}

Use Token

GET https://mcp-devnet.getaether.xyz/auth/session
Authorization: Bearer YOUR_JWT_TOKEN

Troubleshooting

Causes:
  • Signed wrong message (must match exactly)
  • Wrong signature format (use base64, not hex)
  • Challenge expired (> 5 minutes old)
Solution:
  1. Get fresh challenge
  2. Copy message exactly as provided
  3. Sign with correct wallet
  4. Encode signature as base64
Solution: Re-authenticate:
  • On devnet: call wallet_create again
  • On mainnet: session_auth -> sign -> wallet_connect
Rate limit: 5 attempts per 15 minutes per IP+wallet.Solution: Wait 15 minutes or use different wallet/IP.
Your session is still active! Use existing token or:
{ "tool": "session_info" }

Next Steps