Skip to content

konradbachowski/solana-api-key-manager

Repository files navigation

Solana API Key Manager

An on-chain API key management system built with Anchor — demonstrating how Web2 backend patterns map to Solana on-chain programs.

Built for: Solana Hackathon — "Rebuild Backend Systems as On-Chain Rust Programs" Network: Devnet Program ID: 9uvbWGNcfJ74Jv2KZVE56NWeXKb7P4CUzSPwQ7PWQGiB


Architecture overview

This system replaces a traditional backend API key service (think: Stripe, Sendgrid, or any SaaS) with a fully on-chain program. No database, no server, no middleware. Just accounts and instructions.

Authority (owner)
    │
    └── Registry PDA  ["registry", authority]
              │
              ├── ApiKey PDA  ["api_key", registry, 0]   ← key #0
              ├── ApiKey PDA  ["api_key", registry, 1]   ← key #1
              └── ApiKey PDA  ["api_key", registry, N]   ← key #N

Key design choices:

  • The raw API key is never stored on-chain — only its SHA-256 hash
  • PDAs serve as deterministic, ownerless database rows
  • Authorization is enforced by account constraints, not application code
  • Expiry is checked against the Solana Clock sysvar (no trusted timestamp needed)

How this works in Web2

In a traditional Web2 system, API key management looks like:

PostgreSQL database:
┌─────────────────────────────────────────────────────────┐
│ Table: api_registries                                    │
│  id (uuid PK), name, owner_user_id, key_count           │
├─────────────────────────────────────────────────────────┤
│ Table: api_keys                                          │
│  id (uuid PK), registry_id (FK), key_hash, owner,       │
│  permissions, expires_at, is_active, created_at          │
└─────────────────────────────────────────────────────────┘

REST API:
  POST /registries          → create registry (auth: JWT)
  POST /registries/:id/keys → issue key       (auth: JWT)
  DELETE /keys/:id          → revoke key      (auth: JWT)
  GET /keys/:id/verify      → verify key      (public)

Auth middleware:
  - JWT bearer token check on every request
  - Registry ownership check before issue/revoke
  - Hash comparison during verify

How this works on Solana

Every Web2 concept maps directly to a Solana primitive:

Web2 concept Solana equivalent Details
Database table Account type (struct) Registry, ApiKey structs in Rust
Table row PDA Deterministic address derived from seeds
Primary key PDA seeds ["registry", authority], ["api_key", registry, key_id]
Foreign key Stored Pubkey field ApiKey.registry: Pubkey
CRUD API Instructions create_registry, issue_key, revoke_key, verify_key
Auth middleware Account constraints has_one = authority @ Unauthorized
System clock Clock sysvar Clock::get()?.unix_timestamp
Transaction log On-chain event emit!(VerifyEvent { ... })
App server Solana validators Distributed, always-on execution

Program instructions

create_registry(name: String)
  Signers: authority
  Accounts: registry (init PDA), authority (mut), system_program
  Logic: Init Registry PDA with authority, name, key_count=0

issue_key(key_hash: [u8;32], permissions: u64, expires_at: i64)
  Signers: authority
  Accounts: registry (mut), api_key (init PDA), authority (mut), owner, system_program
  Logic: Init ApiKey PDA, increment registry.key_count

revoke_key()
  Signers: authority
  Accounts: registry, api_key (mut), authority
  Logic: Set api_key.is_active = false; verify authority owns registry

verify_key(key_hash: [u8;32])
  Signers: any (or no signer needed if using RPC read)
  Accounts: api_key
  Logic: Check is_active, check expiry vs Clock, check hash match, emit VerifyEvent

Permissions bitmask

Bit Value Meaning
0 1 READ
1 2 WRITE
2 4 ADMIN

Combine: permissions = 3 means READ + WRITE. permissions = 7 means all.


Tradeoffs and constraints

Advantages over Web2:

  • No server downtime — validators are always running
  • Transparent — all key states are publicly verifiable
  • No vendor lock-in — anyone can build a client
  • Immutable audit trail — revocations are on-chain forever

Constraints:

  • Storage cost: Each account requires rent (lamports locked). A Registry costs ~0.001 SOL; each ApiKey ~0.001 SOL.
  • Compute: Every instruction costs compute units. verify_key is free to read off-chain but costs CU if called as a transaction.
  • No secret storage: The raw key cannot be recovered on-chain by design. If you lose it, you must revoke and reissue.
  • No delete: Accounts can be closed (rent reclaimed) but this wasn't implemented in v1. Revoke is permanent.
  • Clock precision: Clock.unix_timestamp is block time, not wall clock. ~400ms slot time means precision is sufficient for key expiry.

Devnet transaction links

Program on devnet: 9uvbWGNcfJ74Jv2KZVE56NWeXKb7P4CUzSPwQ7PWQGiB


Setup and usage

Prerequisites

# Solana CLI
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

# Anchor CLI (macOS)
brew install anchor

# Node.js dependencies
yarn install

Generate or use existing keypair

solana-keygen new -o ~/.config/solana/id.json
solana airdrop 2 <YOUR_PUBKEY> --url devnet

Build and deploy

cd apps/solana-api-key-manager

# Build the program
anchor build

# Deploy to devnet (requires ~2 SOL for deployment)
anchor deploy --provider.cluster devnet

After deploy, update the program ID in Anchor.toml and cli/src/index.ts.

Run tests

yarn install
anchor test --provider.cluster devnet

CLI usage

# Create a registry
npx ts-node cli/src/index.ts create-registry \
  --name "MyService" \
  --keypair ~/.config/solana/id.json \
  --program-id 9uvbWGNcfJ74Jv2KZVE56NWeXKb7P4CUzSPwQ7PWQGiB

# Issue a new API key (generates and prints the raw key — SAVE IT)
npx ts-node cli/src/index.ts issue-key \
  --registry <REGISTRY_PDA> \
  --owner <OWNER_PUBKEY> \
  --permissions 3 \
  --program-id 9uvbWGNcfJ74Jv2KZVE56NWeXKb7P4CUzSPwQ7PWQGiB

# Issue a key with expiry
npx ts-node cli/src/index.ts issue-key \
  --registry <REGISTRY_PDA> \
  --owner <OWNER_PUBKEY> \
  --permissions 1 \
  --expires 2027-01-01 \
  --program-id 9uvbWGNcfJ74Jv2KZVE56NWeXKb7P4CUzSPwQ7PWQGiB

# Verify a key (checks hash + active + not expired)
npx ts-node cli/src/index.ts verify-key \
  --registry <REGISTRY_PDA> \
  --key-id 0 \
  --raw-key <RAW_KEY_FROM_ISSUE_STEP> \
  --program-id 9uvbWGNcfJ74Jv2KZVE56NWeXKb7P4CUzSPwQ7PWQGiB

# Revoke a key
npx ts-node cli/src/index.ts revoke-key \
  --registry <REGISTRY_PDA> \
  --key-id 0 \
  --program-id 9uvbWGNcfJ74Jv2KZVE56NWeXKb7P4CUzSPwQ7PWQGiB

Project structure

solana-api-key-manager/
  programs/
    api-key-manager/
      src/
        lib.rs              # Program entrypoint + declare_id!
        instructions/
          create_registry.rs
          issue_key.rs
          revoke_key.rs
          verify_key.rs
        state/
          registry.rs       # Registry account (PDA)
          api_key.rs        # ApiKey account (PDA)
        errors.rs           # Custom error enum
      Cargo.toml
  tests/
    api-key-manager.ts      # Anchor Mocha integration tests (7 test cases)
  cli/
    src/
      index.ts              # Commander CLI entrypoint
      commands/
        create-registry.ts
        issue-key.ts
        revoke-key.ts
        verify-key.ts
      utils/
        crypto.ts           # SHA-256 hashing
        keypair.ts          # Load keypair from file
  Anchor.toml
  package.json
  tsconfig.json

About

On-chain API key management system built with Anchor — demonstrating how Web2 backend patterns map to Solana programs

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors