Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 47 additions & 16 deletions apps/web/src/components/earnings-estimator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@

import { useMemo, useState } from "react";
import {
DEFAULT_GPU_MODEL,
GPU_GROUPS,
GPU_RATE_CHECKED_AT,
GPU_RATE_REFERENCES,
GPU_RATE_SOURCE,
GPU_RATE_SOURCES,
UTILIZATION_SCENARIOS,
} from "@/lib/gpu-estimates";
import { MARKET_FEE_PCT, OPERATOR_SHARE_PCT } from "@/lib/market";

const DAYS_PER_MONTH = 30;
const OTHER_GPU = "__other__";

function rateFor(model: string): string {
const match = GPU_RATE_REFERENCES.find((gpu) => gpu.model === model);
return match?.hourlyUsd != null ? String(match.hourlyUsd) : "";
}

function usd(value: number): string {
return new Intl.NumberFormat("en-US", {
Expand All @@ -20,8 +28,9 @@ function usd(value: number): string {
}

export function EarningsEstimator({ jobsCompleted24h }: { jobsCompleted24h: number | null }) {
const [model, setModel] = useState(GPU_RATE_REFERENCES[1].model);
const [hourlyRate, setHourlyRate] = useState(String(GPU_RATE_REFERENCES[1].hourlyUsd));
const [model, setModel] = useState(DEFAULT_GPU_MODEL);
const [customModel, setCustomModel] = useState("");
const [hourlyRate, setHourlyRate] = useState(rateFor(DEFAULT_GPU_MODEL));
const [hoursAvailable, setHoursAvailable] = useState("24");

const selected = useMemo(
Expand All @@ -33,28 +42,48 @@ export function EarningsEstimator({ jobsCompleted24h }: { jobsCompleted24h: numb

function updateModel(next: string) {
setModel(next);
const match = GPU_RATE_REFERENCES.find((gpu) => gpu.model === next);
setHourlyRate(match ? String(match.hourlyUsd) : "");
setHourlyRate(rateFor(next));
}

const selectedSource = selected?.source ? GPU_RATE_SOURCES[selected.source] : null;
const rateHint = !selected
? "Custom model: enter a comparable rate."
: selectedSource
? `${selected.vram} · rate from ${selectedSource.label}.`
: `${selected.vram} · no public hourly rental rate for this GPU, so enter your own.`;

return (
<section className="space-y-5 border border-[var(--color-rule)] bg-[var(--color-card)] rounded-md p-4 sm:p-6">
<div className="grid gap-4 sm:grid-cols-2">
<label className="block space-y-2 text-sm">
<span className="text-[var(--color-fg)]">GPU model</span>
<input
{/* A <select>, not an <input list>: a datalist only offers options
matching the input's current text, so a prefilled model hid
every other GPU. */}
<select
aria-label="GPU model"
list="gpu-rate-models"
value={model}
onChange={(event) => updateModel(event.target.value)}
placeholder="Type a model, e.g. RTX 4090"
className="w-full rounded border border-[var(--color-rule)] bg-[var(--color-bg)] px-3 py-2 text-[var(--color-fg)] outline-none focus:border-[var(--color-accent)]"
/>
<datalist id="gpu-rate-models">
{GPU_RATE_REFERENCES.map((gpu) => (
<option key={gpu.model} value={gpu.model}>{`${gpu.model} · ${gpu.vram}`}</option>
>
{GPU_GROUPS.map((group) => (
<optgroup key={group} label={group}>
{GPU_RATE_REFERENCES.filter((gpu) => gpu.group === group).map((gpu) => (
<option key={gpu.model} value={gpu.model}>{`${gpu.model} · ${gpu.vram}`}</option>
))}
</optgroup>
))}
</datalist>
<option value={OTHER_GPU}>Other GPU (enter your own rate)</option>
</select>
{model === OTHER_GPU && (
<input
aria-label="Your GPU model"
value={customModel}
onChange={(event) => setCustomModel(event.target.value)}
placeholder="Your GPU, e.g. RTX 2080 Ti"
className="w-full rounded border border-[var(--color-rule)] bg-[var(--color-bg)] px-3 py-2 text-[var(--color-fg)] outline-none focus:border-[var(--color-accent)]"
/>
)}
</label>

<label className="block space-y-2 text-sm">
Expand All @@ -71,7 +100,7 @@ export function EarningsEstimator({ jobsCompleted24h }: { jobsCompleted24h: numb
className="w-full rounded border border-[var(--color-rule)] bg-[var(--color-bg)] px-3 py-2 text-[var(--color-fg)] outline-none focus:border-[var(--color-accent)]"
/>
<span className="block text-xs text-[var(--color-dim)]">
{selected ? `${selected.vram} reference rate.` : "Custom model: enter a comparable rate."} This is an external rental-price benchmark.
{rateHint} This is an external rental-price benchmark.
</span>
</label>

Expand Down Expand Up @@ -137,8 +166,10 @@ export function EarningsEstimator({ jobsCompleted24h }: { jobsCompleted24h: numb
: `${jobsCompleted24h.toLocaleString()} completed jobs in the last 24 hours. Public payout totals by GPU are not available yet, so there is no actual average to show.`}
</p>
<p className="text-[var(--color-dim)]">
This is a comparison scenario, not a c0mpute payout forecast. c0mpute providers currently bid per job, so realized earnings depend on workload demand, your bids, and how often jobs choose your node. The reference rates are public RunPod GPU prices checked {GPU_RATE_CHECKED_AT}; prices change.
{" "}<a href={GPU_RATE_SOURCE} target="_blank" rel="noopener noreferrer">View current RunPod rates</a>.
This is a comparison scenario, not a c0mpute payout forecast. c0mpute providers currently bid per job, so realized earnings depend on workload demand, your bids, and how often jobs choose your node. The reference rates are public GPU rental prices checked {GPU_RATE_CHECKED_AT}; prices change. Current rates:
{" "}<a href={GPU_RATE_SOURCES["runpod-secure"].url} target="_blank" rel="noopener noreferrer">RunPod</a>,
{" "}<a href={GPU_RATE_SOURCES["vast-median"].url} target="_blank" rel="noopener noreferrer">Vast.ai</a>,
{" "}<a href={GPU_RATE_SOURCES.lambda.url} target="_blank" rel="noopener noreferrer">Lambda</a>.
</p>
</div>
</section>
Expand Down
80 changes: 66 additions & 14 deletions apps/web/src/lib/gpu-estimates.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,78 @@
/**
* Snapshot of public RunPod Community Cloud hourly rates, checked 2026-10-29.
* These are comparison inputs for the scenario calculator, not c0mpute bids
* or observed c0mpute earnings. Update when the public rate card changes.
* Public hourly rental rates for one GPU, used as comparison inputs for the
* scenario calculator. They are not c0mpute bids or observed c0mpute earnings.
*
* Each row names where its rate came from:
* - RunPod: the public GPU types API / rate card. Secure Cloud on-demand price
* when RunPod lists one, otherwise its Community Cloud price.
* - Vast.ai: median on-demand base price ($/h, storage excluded) across
* rentable single-GPU offers, used for cards RunPod does not rent.
* - Lambda: on-demand 1x price from its public rate card.
*
* Cards with no public hourly rental market (AMD Radeon, Apple silicon) carry
* `hourlyUsd: null`: the page asks the operator for their own comparison rate
* instead of inventing one. Update when the public rate cards change.
*/
export const GPU_RATE_SOURCE = "https://www.runpod.io/pricing";
export const GPU_RATE_CHECKED_AT = "2026-10-29";
export const GPU_RATE_CHECKED_AT = "2026-11-08";

export const GPU_RATE_SOURCES = {
"runpod-secure": { label: "RunPod Secure Cloud", url: "https://www.runpod.io/pricing" },
"runpod-community": { label: "RunPod Community Cloud", url: "https://www.runpod.io/pricing" },
"vast-median": { label: "Vast.ai median on-demand offer", url: "https://vast.ai/pricing" },
lambda: { label: "Lambda on-demand", url: "https://lambda.ai/service/gpu-cloud/pricing" },
} as const;

export type GpuRateSourceId = keyof typeof GPU_RATE_SOURCES;

export type GpuGroup = "NVIDIA GeForce" | "AMD Radeon" | "Apple silicon" | "NVIDIA datacenter";

export const GPU_GROUPS: GpuGroup[] = ["NVIDIA GeForce", "AMD Radeon", "Apple silicon", "NVIDIA datacenter"];

export interface GpuRateReference {
model: string;
vram: string;
hourlyUsd: number;
group: GpuGroup;
/** Public comparison rate in USD per hour, or null when no public rental market exists. */
hourlyUsd: number | null;
source: GpuRateSourceId | null;
}

export const GPU_RATE_REFERENCES: GpuRateReference[] = [
{ model: "NVIDIA RTX 3090", vram: "24 GB", hourlyUsd: 0.5 },
{ model: "NVIDIA RTX 4090", vram: "24 GB", hourlyUsd: 0.89 },
{ model: "NVIDIA RTX 5090", vram: "32 GB", hourlyUsd: 1.19 },
{ model: "NVIDIA L40S", vram: "48 GB", hourlyUsd: 1.09 },
{ model: "NVIDIA A100 PCIe", vram: "80 GB", hourlyUsd: 1.79 },
{ model: "NVIDIA A100 SXM", vram: "80 GB", hourlyUsd: 1.79 },
{ model: "NVIDIA H100 PCIe", vram: "80 GB", hourlyUsd: 2.89 },
{ model: "NVIDIA H100 SXM", vram: "80 GB", hourlyUsd: 3.99 },
// Consumer NVIDIA
{ model: "NVIDIA RTX 3060", vram: "12 GB", group: "NVIDIA GeForce", hourlyUsd: 0.07, source: "vast-median" },
{ model: "NVIDIA RTX 3070", vram: "8 GB", group: "NVIDIA GeForce", hourlyUsd: 0.13, source: "runpod-community" },
{ model: "NVIDIA RTX 3080", vram: "10 GB", group: "NVIDIA GeForce", hourlyUsd: 0.17, source: "runpod-community" },
{ model: "NVIDIA RTX 3090", vram: "24 GB", group: "NVIDIA GeForce", hourlyUsd: 0.5, source: "runpod-secure" },
{ model: "NVIDIA RTX 4060 Ti 16GB", vram: "16 GB", group: "NVIDIA GeForce", hourlyUsd: 0.16, source: "vast-median" },
{ model: "NVIDIA RTX 4070", vram: "12 GB", group: "NVIDIA GeForce", hourlyUsd: 0.19, source: "vast-median" },
{ model: "NVIDIA RTX 4070 Ti Super", vram: "16 GB", group: "NVIDIA GeForce", hourlyUsd: 0.21, source: "vast-median" },
{ model: "NVIDIA RTX 4080", vram: "16 GB", group: "NVIDIA GeForce", hourlyUsd: 0.5, source: "runpod-secure" },
{ model: "NVIDIA RTX 4090", vram: "24 GB", group: "NVIDIA GeForce", hourlyUsd: 0.89, source: "runpod-secure" },
{ model: "NVIDIA RTX 5080", vram: "16 GB", group: "NVIDIA GeForce", hourlyUsd: 0.59, source: "runpod-secure" },
{ model: "NVIDIA RTX 5090", vram: "32 GB", group: "NVIDIA GeForce", hourlyUsd: 1.19, source: "runpod-secure" },

// AMD: no public hourly rental market for these cards.
{ model: "AMD Radeon RX 7900 XT", vram: "20 GB", group: "AMD Radeon", hourlyUsd: null, source: null },
{ model: "AMD Radeon RX 7900 XTX", vram: "24 GB", group: "AMD Radeon", hourlyUsd: null, source: null },

// Apple silicon: unified memory (maximum configuration); no public hourly GPU rental market.
{ model: "Apple M2 Max", vram: "up to 96 GB unified", group: "Apple silicon", hourlyUsd: null, source: null },
{ model: "Apple M2 Ultra", vram: "up to 192 GB unified", group: "Apple silicon", hourlyUsd: null, source: null },
{ model: "Apple M3 Max", vram: "up to 128 GB unified", group: "Apple silicon", hourlyUsd: null, source: null },
{ model: "Apple M3 Ultra", vram: "up to 512 GB unified", group: "Apple silicon", hourlyUsd: null, source: null },
{ model: "Apple M4 Max", vram: "up to 128 GB unified", group: "Apple silicon", hourlyUsd: null, source: null },

// Datacenter NVIDIA
{ model: "NVIDIA T4", vram: "16 GB", group: "NVIDIA datacenter", hourlyUsd: 0.14, source: "vast-median" },
{ model: "NVIDIA A10", vram: "24 GB", group: "NVIDIA datacenter", hourlyUsd: 1.29, source: "lambda" },
{ model: "NVIDIA L40S", vram: "48 GB", group: "NVIDIA datacenter", hourlyUsd: 1.09, source: "runpod-secure" },
{ model: "NVIDIA A100 SXM 40GB", vram: "40 GB", group: "NVIDIA datacenter", hourlyUsd: 1.0, source: "runpod-community" },
{ model: "NVIDIA A100 PCIe", vram: "80 GB", group: "NVIDIA datacenter", hourlyUsd: 1.79, source: "runpod-secure" },
{ model: "NVIDIA A100 SXM", vram: "80 GB", group: "NVIDIA datacenter", hourlyUsd: 1.79, source: "runpod-secure" },
{ model: "NVIDIA H100 PCIe", vram: "80 GB", group: "NVIDIA datacenter", hourlyUsd: 2.89, source: "runpod-secure" },
{ model: "NVIDIA H100 SXM", vram: "80 GB", group: "NVIDIA datacenter", hourlyUsd: 3.99, source: "runpod-secure" },
];

export const DEFAULT_GPU_MODEL = "NVIDIA RTX 4090";

export const UTILIZATION_SCENARIOS = [10, 25, 50] as const;
Loading