diff --git a/Cargo.lock b/Cargo.lock index ca98578e..300c3704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,7 +486,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "c0mpute-api" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "c0mpute-proto", @@ -501,7 +501,7 @@ dependencies = [ [[package]] name = "c0mpute-bench" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "blake3", @@ -512,7 +512,7 @@ dependencies = [ [[package]] name = "c0mpute-cli" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "async-trait", @@ -549,7 +549,7 @@ dependencies = [ [[package]] name = "c0mpute-core" -version = "0.3.60" +version = "0.3.61" dependencies = [ "aes-gcm", "anyhow", @@ -603,7 +603,7 @@ dependencies = [ [[package]] name = "c0mpute-doctor" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "serde", @@ -615,7 +615,7 @@ dependencies = [ [[package]] name = "c0mpute-envelope" -version = "0.3.60" +version = "0.3.61" dependencies = [ "blake3", "bs58", @@ -628,7 +628,7 @@ dependencies = [ [[package]] name = "c0mpute-gateway" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "axum", @@ -650,7 +650,7 @@ dependencies = [ [[package]] name = "c0mpute-market" -version = "0.3.60" +version = "0.3.61" dependencies = [ "c0mpute-envelope", "serde", @@ -660,7 +660,7 @@ dependencies = [ [[package]] name = "c0mpute-net" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "async-trait", @@ -681,7 +681,7 @@ dependencies = [ [[package]] name = "c0mpute-placement" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "async-trait", @@ -702,7 +702,7 @@ dependencies = [ [[package]] name = "c0mpute-proto" -version = "0.3.60" +version = "0.3.61" dependencies = [ "blake3", "hex", @@ -714,7 +714,7 @@ dependencies = [ [[package]] name = "c0mpute-sealed" -version = "0.3.60" +version = "0.3.61" dependencies = [ "aes", "aes-gcm", @@ -736,7 +736,7 @@ dependencies = [ [[package]] name = "c0mpute-secure-chat" -version = "0.3.60" +version = "0.3.61" dependencies = [ "aes-gcm", "anyhow", @@ -759,7 +759,7 @@ dependencies = [ [[package]] name = "c0mpute-store" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "blake3", @@ -778,7 +778,7 @@ dependencies = [ [[package]] name = "c0mpute-transcode" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "c0mpute-proto", @@ -791,7 +791,7 @@ dependencies = [ [[package]] name = "c0mpute-update" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "hex", @@ -806,7 +806,7 @@ dependencies = [ [[package]] name = "c0mpute-verify" -version = "0.3.60" +version = "0.3.61" dependencies = [ "anyhow", "blake3", diff --git a/Cargo.toml b/Cargo.toml index c59c60e2..1bbfb82f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ ] [workspace.package] -version = "0.3.60" +version = "0.3.61" edition = "2024" license = "MIT" repository = "https://github.com/profullstack/c0mpute" diff --git a/apps/web/public/llms.txt b/apps/web/public/llms.txt index 500b9036..f8bc867e 100644 --- a/apps/web/public/llms.txt +++ b/apps/web/public/llms.txt @@ -11,7 +11,7 @@ - [Chat](https://c0mpute.com/chat): chat with open models served by c0mpute/Infernet provider nodes. Sign in with CoinPay (a CoinPay DID is required). Same chat from the CLI (`c0mpute chat login`, `c0mpute chat "prompt"`), an OpenAI-compatible API (base URL `https://c0mpute.com/v1`, `Authorization: Bearer c0k_...`), and MCP (`https://c0mpute.com/api/mcp`, tools chat, list_models, whoami; stdio via `c0mpute chat mcp`). - [Run a node](https://c0mpute.com/providers): `curl -fsSL https://c0mpute.com/install.sh | sh` installs c0mpute and starts the worker (Linux, macOS, Windows via WSL2; CPU or GPU). The goal is 100,000 nodes, enough pooled capacity to train and serve open-weight models in the open. - [CPU and GPU earnings estimates](https://c0mpute.com/estimates): compare illustrative monthly scenarios using public cloud rental rates and assumed paid utilization. CPU reference rates are DigitalOcean Basic shared-CPU VM prices, including RAM, storage, and transfer. These are not actual c0mpute payouts or guaranteed earnings; the public status does not attribute jobs or payouts by hardware. -- [Market](https://c0mpute.com/market): GPU nodes online with each operator's listing. Fully competitive: every job is a sealed-bid auction under the buyer's cap and the lowest bid wins automatically; no rate card. `c0mpute pricing set --min ` = dynamic pricing (starts at 60% of the cap, undercuts the winning price by 2% after a loss, probes up after a win, rises with queue load) never under the floor; 5% market fee on market jobs (the operator keeps 95%; Infernet charges the same 5% on paid inference and the two never stack); `--fixed ` = one price. The listing (`pricing: {mode, min_usd, fixed_usd}`) is in https://c0mpute.com/api/nodes. +- [Market](https://c0mpute.com/market): GPU nodes online with each operator's listing. Fully competitive: every job is a sealed-bid auction under the buyer's cap and the lowest bid wins automatically; no rate card. New nodes default to a $0.001 minimum per job; existing node settings are preserved. `c0mpute pricing set --min ` = dynamic pricing (starts at 60% of the cap, undercuts the winning price by 2% after a loss, probes up after a win, rises with queue load) never under the floor; 5% market fee on market jobs (the operator keeps 95%; Infernet charges the same 5% on paid inference and the two never stack); `--fixed ` = one price. The listing (`pricing: {mode, min_usd, fixed_usd}`) is in https://c0mpute.com/api/nodes. - [Node board](https://c0mpute.com/nodes): every node online now, ranked by `c0mpute bench` score, with the operator's nickname. JSON at https://c0mpute.com/api/nodes (`name` per node, `operators` = nodes online per nickname). - Node nicknames: `c0mpute name ` then restart the worker (`c0mpute worker stop && c0mpute worker start -d`), or install with `curl -fsSL https://c0mpute.com/install.sh | sh -s -- --name `. Stored in `~/.c0mpute/name`; `C0MPUTE_NAME` overrides it. Lowercase a-z, 0-9, - and _, up to 32 chars. Public and unverified; earnings follow the payout address, not the name. Needs 0.2.64+. - [Benchmarks](https://c0mpute.com/benchmark): live CPU, cores, RAM, composite score and per-workload throughput for online nodes. These synthetic CPU workloads do not measure AI model inference speed. diff --git a/apps/web/src/app/market/page.tsx b/apps/web/src/app/market/page.tsx index 75528c25..91a26bbe 100644 --- a/apps/web/src/app/market/page.tsx +++ b/apps/web/src/app/market/page.tsx @@ -156,7 +156,9 @@ $ infernet payout set USDC 0xYourAddress --network polygon`} winning price by {Math.round(UNDERCUT * 100)}% after it loses to a cheaper bid, edges back up after it wins, and asks more as its job queue fills. All you set is a minimum: it never bids under it, and - jobs capped below it are skipped. Or ask one fixed price. + jobs capped below it are skipped. New nodes default to a $0.001 + minimum per job; existing node settings are preserved. Or ask one + fixed price.

{`$ c0mpute pricing set --min 0.05 # dynamic, never under $0.05 a job $ c0mpute pricing set --fixed 0.40 # always $0.40 a job diff --git a/apps/web/src/app/providers/page.tsx b/apps/web/src/app/providers/page.tsx index 691f5420..2688c66b 100644 --- a/apps/web/src/app/providers/page.tsx +++ b/apps/web/src/app/providers/page.tsx @@ -179,7 +179,9 @@ $ c0mpute worker start --roles tts,stt`} cap, nodes bid under it, and the lowest bid wins. Your node prices itself by default (it learns the winning price, undercuts it, and asks more when busy) and never goes under the minimum you set with{" "} - c0mpute pricing set --min 0.05; or ask one fixed price. + c0mpute pricing set --min 0.05. New nodes start with a + $0.001 minimum per job; existing settings are preserved. Or ask one + fixed price. When you win, your node runs the job and publishes a receipt. Settlement through CoinPay is built for hosting and ads but{" "} off by default while it is diff --git a/node/crates/c0mpute-cli/src/main.rs b/node/crates/c0mpute-cli/src/main.rs index 8059385d..26307b0e 100644 --- a/node/crates/c0mpute-cli/src/main.rs +++ b/node/crates/c0mpute-cli/src/main.rs @@ -110,6 +110,7 @@ enum Cmd { /// /// Dynamic (default) undercuts the buyer's cap when idle and rises as the /// queue fills; fixed always asks one price. Neither goes under `--min`. + /// New nodes default to a $0.001 minimum per job; explicit settings persist. /// A running worker picks a change up on restart. Pricing { #[command(subcommand)] diff --git a/node/crates/c0mpute-core/src/config.rs b/node/crates/c0mpute-core/src/config.rs index 87c40e53..7308663e 100644 --- a/node/crates/c0mpute-core/src/config.rs +++ b/node/crates/c0mpute-core/src/config.rs @@ -1031,6 +1031,12 @@ impl Config { mod tests { use super::*; + #[test] + fn a_new_partial_config_gets_the_default_pricing_floor() { + let cfg: Config = toml::from_str("roles = []\n").unwrap(); + assert_eq!(cfg.pricing.min_usd, 0.001); + } + #[test] fn gateways_opt_into_adult_and_an_old_serve_key_is_ignored() { assert!(!Config::default().adult.buyer); diff --git a/node/crates/c0mpute-core/src/pricing.rs b/node/crates/c0mpute-core/src/pricing.rs index 6c8fd289..af56943d 100644 --- a/node/crates/c0mpute-core/src/pricing.rs +++ b/node/crates/c0mpute-core/src/pricing.rs @@ -1,6 +1,7 @@ //! How a worker prices its bids: the operator's listing. //! -//! The operator sets a floor (`min_usd`, per job) and a mode: +//! The operator sets a floor (`min_usd`, per job) and a mode. A new node's +//! floor defaults to [`DEFAULT_MINIMUM_USD`]: //! //! - `dynamic` (the default): the node prices itself against the market. //! It starts at [`DYNAMIC_IDLE_SHARE`] of the buyer's cap and learns from @@ -27,6 +28,9 @@ pub const UNDERCUT: f64 = 0.02; /// upward, so the market does not only ratchet down). pub const WIN_PROBE: f64 = 0.05; +/// New nodes start at a one-tenth-cent minimum bid per job. +pub const DEFAULT_MINIMUM_USD: f64 = 0.001; + /// What a dynamic node has learned about the clearing price for one /// workload, as a share of the buyer's cap. Buyers award the lowest bid /// (`buyer.rs`) and broadcast the `JobAccept` with the agreed price, so @@ -76,18 +80,32 @@ impl PricingMode { } } -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PricingConfig { #[serde(default)] pub mode: PricingMode, /// The lowest price per job this node accepts, in USD. 0 = no floor. - #[serde(default)] + #[serde(default = "default_minimum_usd")] pub min_usd: f64, /// The price per job in `fixed` mode, in USD. #[serde(default, skip_serializing_if = "Option::is_none")] pub fixed_usd: Option, } +impl Default for PricingConfig { + fn default() -> Self { + Self { + mode: PricingMode::default(), + min_usd: DEFAULT_MINIMUM_USD, + fixed_usd: None, + } + } +} + +fn default_minimum_usd() -> f64 { + DEFAULT_MINIMUM_USD +} + impl PricingConfig { /// Reject negative or non-finite numbers and a fixed mode with no price. pub fn validate(&self) -> Result<(), String> { @@ -175,6 +193,15 @@ mod tests { assert_eq!(dynamic(1.5).quote(1.0, 0.0), None); } + #[test] + fn new_nodes_default_to_a_one_tenth_cent_floor() { + let p = PricingConfig::default(); + assert_eq!(p.mode, PricingMode::Dynamic); + assert_eq!(p.min_usd, 0.001); + assert_eq!(p.quote(0.001, 0.0), Some(0.001)); + assert_eq!(p.quote(0.0005, 0.0), None); + } + #[test] fn fixed_quotes_its_price_and_respects_the_cap() { let p = PricingConfig { @@ -250,9 +277,12 @@ mod tests { let p: PricingConfig = toml::from_str("").unwrap(); assert_eq!(p, PricingConfig::default()); assert_eq!(p.mode, PricingMode::Dynamic); + assert_eq!(p.min_usd, 0.001); let p: PricingConfig = toml::from_str("mode = \"fixed\"\nmin_usd = 0.1\nfixed_usd = 0.2").unwrap(); assert_eq!(p.quote(1.0, 0.0), Some(0.2)); + let explicit_zero: PricingConfig = toml::from_str("min_usd = 0.0").unwrap(); + assert_eq!(explicit_zero.min_usd, 0.0); } #[test]