Concepts
A deeper look at how MachineMarket works under the hood: architecture, providers, templates, instance lifecycle, and security.
Architecture overview
┌─────────────────────────────────────────────────────────────┐
│ AI Agent │
│ 1. GET /v1/pricing → calculate cost │
│ 2. Send USDC on Base → ERC-20 transfer │
│ 3. POST /v1/spawn { tx_hash, tier, template, duration } │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MachineMarket API │
│ Next.js App Router (API routes under /api/v1/*) │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Payment │ │ Provider │ │ Supabase │ │
│ │ Verifier │ │ Registry │ │ (instances, txs) │ │
│ │ (viem) │ │ (Hetzner) │ │ │ │
│ └──────────┘ └──────────────┘ └────────────────────┘ │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌──────────────┐ ┌─────────────┐
│ Base Chain │ │ Hetzner │
│ (verify tx) │ │ Cloud API │
└──────────────┘ └─────────────┘Provider system
MachineMarket uses a provider registry pattern. Currently the only provider is Hetzner, but the architecture supports adding new providers (AWS, DigitalOcean, etc.) behind the same interface:
interface VpsProvider {
createServer(config: CreateServerConfig): Promise<ServerResult>;
getServer(providerId: string): Promise<ServerStatus>;
deleteServer(providerId: string): Promise<void>;
}The provider maps MachineMarket tiers to Hetzner server types:
- Nano → cpx11 (2 vCPU, 4 GB, 40 GB)
- Small → cpx21 (4 vCPU, 8 GB, 80 GB)
- Medium → cpx31 (8 vCPU, 16 GB, 160 GB)
- Large → cpx41 (16 vCPU, 32 GB, 320 GB)
Templates
Each template is a cloud-init script that runs on first boot. All templates start with Ubuntu 24.04 and install Docker + SSH.
base
Ubuntu 24.04, Docker, SSH, core utilities (curl, wget, git, jq, htop). A clean slate for any workload.
node
Everything in base plus Node.js 22, pnpm, and PM2. Ready for JavaScript/TypeScript services.
python
Everything in base plus Python 3.12, pip, and venv. Ready for ML workloads and data processing.
agent
Everything in base plus Node.js 22, pnpm, tmux, and git. Designed for deploying other AI agents.
SSH key generation
When spawning an instance, you can either:
- Provide your own public key via the
ssh_pubkeyfield. The API installs it on the server. - Let the API generate a keypair. The API creates an Ed25519 keypair, installs the public key, and returns the private key in the response. Store it — it's only returned once.
Instance lifecycle
spawn()
│
▼
┌─────────────────┐
│ provisioning │
│ (no IP yet) │
└────────┬────────┘
│ server ready
▼
┌─────────────────┐
│ running │◄── extend()
│ (IP assigned) │
└───┬─────────┬───┘
│ │
destroy() │ │ expires_at reached
▼ ▼
┌──────────┐ ┌──────────┐
│ destroyed │ │ expired │
└──────────┘ └──────────┘- provisioning — Server is being created at Hetzner. IP may not be assigned yet. Poll
GET /v1/instances/:iduntil status becomesrunning. - running — Server is active and accessible via SSH. Can be extended with additional payment.
- expired — The
expires_attimestamp passed. A cron job marks it and deletes the Hetzner server. - destroyed — Agent called
DELETE /v1/instances/:id. Server is immediately removed.
Auto-teardown
A cron endpoint (/api/v1/cron/teardown) runs periodically to find instances past their expires_at time. It deletes the Hetzner server and marks the instance as expired.
Security model
- No API keys — Payment is authentication. The tx_hash proves you paid; the wallet_address identifies you.
- On-chain verification — The API reads the transaction receipt and Transfer event directly from Base. It verifies sender, recipient, and amount.
- Deduplication — Each tx_hash can only be used once, preventing replay attacks.
- Wallet limits — Max 10 concurrent instances per wallet to prevent abuse.
- SSH access only — Servers are accessed via SSH. No web panel or shared credentials.