From 720849ec1e8792d1756955a89c9adf383a1527d2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 14:24:56 +0000 Subject: [PATCH] =?UTF-8?q?Remove=20daily=20allowance=20=E2=80=94=20all=20?= =?UTF-8?q?metered=20ops=20require=20credits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browsing (news, blog, video, markets) remains included. AI, search, weather, places, mail all require credits. No daily quota. Simpler economics: every API call that costs us money costs the user credits. Removed: DailyQuota checks from CheckQuota, UseQuota calls from weather/places, paidOnly function, quota UI from wallet page, quota references from all docs. https://claude.ai/code/session_01GRGLA9yj7BpqKiyi6xFwnm --- README.md | 2 +- admin/console.go | 3 +-- docs/ENVIRONMENT_VARIABLES.md | 4 +--- docs/MCP.md | 4 ++-- docs/SYSTEM_DESIGN.md | 2 +- docs/VISION.md | 3 +-- docs/WALLET_AND_CREDITS.md | 42 +++++++++-------------------------- docs/WHITEPAPER.md | 6 ++--- places/places.go | 16 +++++-------- wallet/handlers.go | 34 ++++++---------------------- wallet/wallet.go | 23 +------------------ weather/weather.go | 16 +++++-------- 12 files changed, 39 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index c568811b..9ecea63a 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ See [API docs](https://mu.xyz/api) · [MCP docs](docs/MCP.md) ## Pricing -Browsing is included. AI features use credits — 100/day with every account, then pay as you go from 1p per query. +Browsing is included. AI and search features use credits — 1 credit = 1p, pay as you go. - **Card** — Top up via Stripe. 1 credit = 1p. - **Crypto** — AI agents pay per-request with USDC via [x402](https://x402.org). No account needed. diff --git a/admin/console.go b/admin/console.go index 9a178efe..f2a1d24e 100644 --- a/admin/console.go +++ b/admin/console.go @@ -183,10 +183,9 @@ func runCommand(cmd string) string { return "usage: wallet " } w := wallet.GetWallet(arg(1)) - usage := wallet.GetDailyUsage(arg(1)) txns := wallet.GetTransactions(arg(1), 10) var sb strings.Builder - sb.WriteString(fmt.Sprintf("Balance: %d credits\nDaily usage: %d / %d\n", w.Balance, usage.Used, wallet.DailyQuota)) + sb.WriteString(fmt.Sprintf("Balance: %d credits\n", w.Balance)) if len(txns) > 0 { sb.WriteString("\nRecent transactions:\n") for _, tx := range txns { diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md index 8f7ef1eb..c40e6cf0 100644 --- a/docs/ENVIRONMENT_VARIABLES.md +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -122,8 +122,7 @@ export DONATION_URL="https://gocardless.com/your-donation-link" ## Quota Configuration ```bash -# Daily AI queries per account (default: 10) -export DAILY_QUOTA="100" +# Credit costs per operation (defaults shown) # Credit costs per operation (default values shown) export CREDIT_COST_NEWS="1" # News search (1p) @@ -190,7 +189,6 @@ export MAIL_SELECTOR="default" | `X402_ASSETS` | `USDC,EURC` | Accepted tokens (comma-separated symbols) | | `X402_FACILITATOR_URL` | `https://x402.org/facilitator` | x402 facilitator endpoint | | `X402_NETWORK` | `eip155:8453` | Blockchain network for x402 payments | -| `DAILY_QUOTA` | `100` | Daily AI queries per account | | `CREDIT_COST_NEWS` | `1` | Credits per news search | | `CREDIT_COST_VIDEO` | `2` | Credits per video search | | `CREDIT_COST_VIDEO_WATCH` | `0` | Credits per video watch (free by default) | diff --git a/docs/MCP.md b/docs/MCP.md index 237c3d17..db463076 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -120,7 +120,7 @@ Both return a session token. Use it in subsequent requests: Authorization: Bearer SESSION_TOKEN ``` -Every account includes **100 credits per day** and can top up with a card via Stripe. +Accounts can top up credits with a card via Stripe. ## Available Tools @@ -210,7 +210,7 @@ curl -X POST https://mu.xyz/mcp \ | Auth header | `X-PAYMENT` | `Authorization: Bearer` | | Payment model | Per request | Pre-paid credits | | Currency | USDC | GBP | -| Daily allowance | No | 100 queries/day | +| Billing | Per request | Pre-paid credits | | Best for | Autonomous agents | Human users, MCP clients | ## Self-Hosting diff --git a/docs/SYSTEM_DESIGN.md b/docs/SYSTEM_DESIGN.md index 98fba659..75aeeda2 100644 --- a/docs/SYSTEM_DESIGN.md +++ b/docs/SYSTEM_DESIGN.md @@ -233,7 +233,7 @@ Live financial data. Credit-based usage metering. -- **Pay as you go** — 100 credits/day included, then 1 credit = 1p +- **Pay as you go** — 1 credit = 1p, browsing included - **Stripe payments** - Card top-up - **Quota enforcement** - Integrated with API and agent - **Transaction tracking** - Usage history diff --git a/docs/VISION.md b/docs/VISION.md index 2e3a8c4e..31fdd2b6 100644 --- a/docs/VISION.md +++ b/docs/VISION.md @@ -60,8 +60,7 @@ AI agents can pay per-request with USDC through the [x402 protocol](https://x402 ## Pricing - **Browsing included** — news, blogs, videos, markets -- **100 credits per day** — covers search, chat, and AI features -- **Pay as you go** — 1 credit = 1p, top up via card +- **Pay as you go** — AI and search use credits, 1 credit = 1p - **Crypto** — AI agents pay per-request via [x402](https://x402.org) - **Self-host** — run your own instance, no restrictions diff --git a/docs/WALLET_AND_CREDITS.md b/docs/WALLET_AND_CREDITS.md index 10f0175d..da495b2a 100644 --- a/docs/WALLET_AND_CREDITS.md +++ b/docs/WALLET_AND_CREDITS.md @@ -6,9 +6,8 @@ Mu is a tool, not a destination. Like Google Search in 2000 — you arrive with Credits are a straightforward way to pay for what you use. No dark patterns, no pressure to upgrade, no "unlimited" tiers that incentivize us to maximize your engagement. -- **Daily allowance**: 100 AI queries/day — enough for daily use -- **Pay-as-you-go**: Top up with a card or pay per-request with crypto -- **Self-host**: Run your own instance for free, forever +- **Pay as you go**: Top up with a card or pay per-request with crypto +- **Self-host**: Run your own instance, no restrictions We charge because LLMs and APIs cost money. Here's our actual cost breakdown — we're not extracting margin, just covering infrastructure. @@ -21,15 +20,6 @@ We charge because LLMs and APIs cost money. Here's our actual cost breakdown — - Top up via card payment (Stripe) or pay per-request with crypto (x402) - Credits never expire -### Daily Allowance - -Every account includes **100 credits per day**: -- Resets at midnight UTC -- Covers news search, video search, and chat AI queries -- No payment required - -This should be enough if you're using Mu as a utility. If you need more, pay-as-you-go. - ### Credit Costs | Feature | Cost | Why | @@ -50,11 +40,11 @@ This should be enough if you're using Mu as a utility. If you need more, pay-as- ### Who Pays What -| User Type | Daily Allowance | Credits | Notes | -|-----------|------------|---------|-------| -| Guest | 0 | N/A | Must register | -| Registered | 100 credits | Pay-as-you-go | When daily allowance used | -| Admin | Unlimited | Not needed | Site administrators | +| User Type | Credits | Notes | +|-----------|---------|-------| +| Guest | N/A | Must register | +| Registered | Pay as you go | Top up via card or crypto | +| Admin | Unlimited | Site administrators | ## Why No "Unlimited" Tier? @@ -175,16 +165,6 @@ type Transaction struct { } ``` -### Daily Usage - -```go -type DailyUsage struct { - UserID string `json:"user_id"` - Date string `json:"date"` // "2006-01-02" - Used int `json:"used"` // Quota used today -} -``` - --- ## API Endpoints @@ -215,8 +195,7 @@ X402_FACILITATOR_URL="https://x402.org/facilitator" X402_NETWORK="eip155:8453" X402_ASSET="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" -# Quota (optional - these are defaults) -DAILY_QUOTA="100" +# Credit costs (optional - these are defaults) CREDIT_COST_NEWS="1" CREDIT_COST_NEWS_SUMMARY="1" CREDIT_COST_VIDEO="2" @@ -238,9 +217,8 @@ CREDIT_COST_WEATHER_POLLEN="1" 1. User initiates search/chat 2. Check for x402 payment header → verify and settle on-chain (no account needed) 3. Check if admin → allow (no charge) -4. Check daily quota → allow if available, decrement -5. Check wallet balance → allow if sufficient, deduct credits -6. Otherwise → return 402 with payment requirements (if x402 enabled) or show "quota exceeded" +4. Check wallet balance → allow if sufficient, deduct credits +5. Otherwise → return 402 with payment requirements (if x402 enabled) or show "credits required" --- diff --git a/docs/WHITEPAPER.md b/docs/WHITEPAPER.md index c05812fb..17c50601 100644 --- a/docs/WHITEPAPER.md +++ b/docs/WHITEPAPER.md @@ -74,9 +74,7 @@ Each service does one thing. There are no social feeds combining heterogeneous c The revenue model determines platform behaviour. Advertising-funded platforms are structurally incentivised to maximise attention. Subscription platforms are incentivised to maximise perceived value, which leads to feature bloat and engagement optimisation. -Mu uses per-use micropayments. The platform is incentivised to build tools that solve the user's problem as quickly as possible — the opposite of engagement maximisation. Browsing is included. Only operations that consume infrastructure (API calls, LLM inference, SMTP delivery) carry a cost. Every account includes a daily quota for casual use. - -Operations that create public content or reach external systems (email, blog posts, web fetching) always require real credits, even within the daily quota. This prevents abuse at scale. +Mu uses per-use micropayments. The platform is incentivised to build tools that solve the user's problem as quickly as possible — the opposite of engagement maximisation. Browsing is included. Only operations that consume infrastructure (API calls, LLM inference, SMTP delivery) carry a cost. Users pay for what they use, nothing more. ### 2.4 Self-Hosting @@ -138,7 +136,7 @@ Read-only operations — browsing news feeds, reading blog posts, watching video ### 3.3 Daily Quota -Each account includes a daily allocation of one hundred queries, resetting at midnight UTC. This quota is sufficient for casual utility use. When the daily quota is exhausted, subsequent operations consume credits from the user's wallet. This model ensures accessibility while covering infrastructure costs for heavy usage. +All metered operations consume credits from the user's wallet. Browsing (news, blogs, videos, markets) is included at no cost. Operations that require infrastructure — AI inference, web search, email delivery — cost credits. This model ensures sustainability while keeping the platform accessible. ### 3.4 Incentive Alignment diff --git a/places/places.go b/places/places.go index c722ac3f..f250dc12 100644 --- a/places/places.go +++ b/places/places.go @@ -395,7 +395,7 @@ func handleSearch(w http.ResponseWriter, r *http.Request) { } // Check quota - canProceed, useFree, cost, _ := wallet.CheckQuota(acc.ID, wallet.OpPlacesSearch) + canProceed, _, cost, _ := wallet.CheckQuota(acc.ID, wallet.OpPlacesSearch) if !canProceed { if app.WantsJSON(r) { app.RespondError(w, http.StatusPaymentRequired, "Insufficient credits. Top up your wallet to continue.") @@ -464,10 +464,8 @@ func handleSearch(w http.ResponseWriter, r *http.Request) { sortBy := formValue("sort") sortPlaces(results, sortBy) - // Consume quota after successful operation - if useFree { - wallet.UseQuota(acc.ID) - } else if cost > 0 { + // Deduct credits + if cost > 0 { wallet.DeductCredits(acc.ID, cost, wallet.OpPlacesSearch, map[string]interface{}{"query": query}) } @@ -516,7 +514,7 @@ func handleNearby(w http.ResponseWriter, r *http.Request) { } // Check quota - canProceed, useFree, cost, _ := wallet.CheckQuota(acc.ID, wallet.OpPlacesNearby) + canProceed, _, cost, _ := wallet.CheckQuota(acc.ID, wallet.OpPlacesNearby) if !canProceed { if app.WantsJSON(r) { app.RespondError(w, http.StatusPaymentRequired, "Insufficient credits. Top up your wallet to continue.") @@ -583,10 +581,8 @@ func handleNearby(w http.ResponseWriter, r *http.Request) { sortBy := formValue("sort") sortPlaces(results, sortBy) - // Consume quota after successful operation - if useFree { - wallet.UseQuota(acc.ID) - } else if cost > 0 { + // Deduct credits + if cost > 0 { wallet.DeductCredits(acc.ID, cost, wallet.OpPlacesNearby, map[string]interface{}{ "lat": lat, "lon": lon, "radius": radius, }) diff --git a/wallet/handlers.go b/wallet/handlers.go index 2095d165..ff7edfc4 100644 --- a/wallet/handlers.go +++ b/wallet/handlers.go @@ -14,8 +14,6 @@ import ( // WalletPage renders the wallet page HTML func WalletPage(userID string) string { wallet := GetWallet(userID) - usage := GetDailyUsage(userID) - freeRemaining := GetQuotaRemaining(userID) transactions := GetTransactions(userID, 20) // Check if user is admin @@ -41,19 +39,6 @@ func WalletPage(userID string) string { sb.WriteString(``) if !isAdmin { - // Daily quota - sb.WriteString(`
`) - sb.WriteString(`

Daily Queries

`) - usedPct := float64(usage.Used) / float64(DailyQuota) * 100 - if usedPct > 100 { - usedPct = 100 - } - sb.WriteString(`
`) - sb.WriteString(fmt.Sprintf(`
`, usedPct)) - sb.WriteString(`
`) - sb.WriteString(fmt.Sprintf(`

%d of %d remaining · Resets midnight UTC

`, freeRemaining, DailyQuota)) - sb.WriteString(`
`) - // Self-hosting note sb.WriteString(`
`) sb.WriteString(`

Self-Host

`) @@ -140,14 +125,10 @@ func QuotaExceededPage(operation string, cost int) string { var sb strings.Builder sb.WriteString(`
`) - sb.WriteString(`

Daily Limit Reached

`) - sb.WriteString(`

You've used your daily queries.

`) - sb.WriteString(`

Options

`) - sb.WriteString(`
    `) - sb.WriteString(`
  • Wait until midnight UTC for your quota to reset
  • `) - sb.WriteString(fmt.Sprintf(`
  • Use credits (%d credit%s for this)
  • `, cost, pluralize(cost))) - sb.WriteString(`
  • Add credits
  • `) - sb.WriteString(`
`) + sb.WriteString(`

Credits Required

`) + sb.WriteString(fmt.Sprintf(`

This costs %d credit%s. `, cost, pluralize(cost))) + sb.WriteString(`Add credits to continue.

`) + sb.WriteString(`

1 credit = 1p · View wallet

`) sb.WriteString(`
`) return sb.String() @@ -230,7 +211,7 @@ func PublicWalletPage() string { // Intro sb.WriteString(`
`) sb.WriteString(`

Credits & Pricing

`) - sb.WriteString(`

Every account includes ` + fmt.Sprintf("%d", DailyQuota) + ` queries/day. Need more? Top up and pay as you go — no subscription required.

`) + sb.WriteString(`

Browsing is included. AI and search features use credits. Top up and pay as you go — no subscription required.

`) sb.WriteString(`

Login to view your balance Sign up

`) sb.WriteString(`
`) @@ -630,8 +611,7 @@ func handlePricing(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]interface{}{ "currency": "GBP", "credit_value": "£0.01", - "daily_allowance": DailyQuota, - "operations": items, + "operations": items, }) return } @@ -639,7 +619,7 @@ func handlePricing(w http.ResponseWriter, r *http.Request) { var sb strings.Builder sb.WriteString(`
`) sb.WriteString(`

Pricing

`) - sb.WriteString(`

1 credit = £0.01. Daily allowance: ` + fmt.Sprintf("%d", DailyQuota) + ` credits.

`) + sb.WriteString(`

1 credit = £0.01. Browsing included. AI and search use credits.

`) sb.WriteString(``) sb.WriteString(``) for _, item := range items { diff --git a/wallet/wallet.go b/wallet/wallet.go index 1bc0f9b0..de77be12 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -458,20 +458,9 @@ func GetOperationCost(operation string) int { } } -// paidOnly lists operations that cannot use the daily quota -// and always require real credits. These are actions with spam or -// abuse potential: sending messages, publishing content, or using -// the server as a proxy to fetch external URLs. -func paidOnly(operation string) bool { - switch operation { - case OpMailSend, OpExternalEmail, OpBlogCreate, OpWebFetch: - return true - } - return false -} // CheckQuota checks if a user can perform an operation -// Returns: canProceed, useQuota, creditCost, error +// Returns: canProceed, useQuota (always false now), creditCost, error func CheckQuota(userID string, operation string) (bool, bool, int, error) { // Get account to check admin status acc, err := auth.GetAccount(userID) @@ -491,11 +480,6 @@ func CheckQuota(userID string, operation string) (bool, bool, int, error) { cost := GetOperationCost(operation) - // Some operations always require real credits (e.g. email) - if !paidOnly(operation) && HasQuota(userID) { - return true, true, 0, nil - } - // Check if user has sufficient credits balance := GetBalance(userID) if balance >= cost { @@ -549,11 +533,6 @@ func ConsumeQuota(userID string, operation string) error { return nil } - // Try daily quota first - if HasQuota(userID) { - return UseQuota(userID) - } - // Deduct credits cost := GetOperationCost(operation) return DeductCredits(userID, cost, operation, nil) diff --git a/weather/weather.go b/weather/weather.go index ebbd97e5..2604e6bc 100644 --- a/weather/weather.go +++ b/weather/weather.go @@ -112,8 +112,8 @@ func handleJSON(w http.ResponseWriter, r *http.Request) { includePollen := r.URL.Query().Get("pollen") == "1" - // Check quota for weather forecast - canProceed, useFree, cost, _ := wallet.CheckQuota(acc.ID, wallet.OpWeatherForecast) + // Check credits + canProceed, _, cost, _ := wallet.CheckQuota(acc.ID, wallet.OpWeatherForecast) if !canProceed { app.RespondError(w, http.StatusPaymentRequired, "Insufficient credits. Top up your wallet to continue.") return @@ -126,10 +126,8 @@ func handleJSON(w http.ResponseWriter, r *http.Request) { return } - // Consume weather quota - if useFree { - wallet.UseQuota(acc.ID) - } else if cost > 0 { + // Deduct credits + if cost > 0 { wallet.DeductCredits(acc.ID, cost, wallet.OpWeatherForecast, nil) } @@ -139,14 +137,12 @@ func handleJSON(w http.ResponseWriter, r *http.Request) { // Fetch pollen if requested and quota allows if includePollen { - canPollenProceed, usePollenFree, pollenCost, _ := wallet.CheckQuota(acc.ID, wallet.OpWeatherPollen) + canPollenProceed, _, pollenCost, _ := wallet.CheckQuota(acc.ID, wallet.OpWeatherPollen) if canPollenProceed { pollen, pollenErr := FetchPollen(lat, lon) if pollenErr == nil { result["pollen"] = pollen - if usePollenFree { - wallet.UseQuota(acc.ID) - } else if pollenCost > 0 { + if pollenCost > 0 { wallet.DeductCredits(acc.ID, pollenCost, wallet.OpWeatherPollen, nil) } }
News, blogs, videosincluded