OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / core / incentive / credits / pricing.js

275 lines · javascript ref:
/**
 * Pricing Engine
 *
 * Token-based pricing for HiveRelay services.
 * Designed to be 5-10x cheaper than cloud AI APIs (Claude, ChatGPT, etc.)
 * because relay operators serve local models at near-zero marginal cost.
 *
 * Pricing philosophy:
 *   - Local LLM inference has ~zero marginal cost per token
 *   - Operators pay fixed costs: hardware, electricity, bandwidth
 *   - Credits are cheap enough that devs don't think twice
 *   - Volume discounts via CreditManager bonus schedule
 *
 * Comparison (at BTC ≈ $60,000, 1 sat ≈ $0.0006):
 *
 *   Service          | HiveRelay          | Claude API         | OpenAI API
 *   ai.infer (1K in) | 1 sat ($0.0006)    | $0.003             | $0.0025
 *   ai.infer (1K out)| 2 sats ($0.0012)   | $0.015             | $0.010
 *   ai.embed (1K)    | 0.5 sat ($0.0003)  | $0.0001            | $0.00002
 *   storage.write    | 1 sat ($0.0006)     | S3 ~$0.005/1K PUT  | --
 *
 *   → HiveRelay AI inference is ~5x cheaper than Claude, ~4x cheaper than OpenAI
 *   → Embeddings are priced higher than cloud (local GPU vs cloud batch)
 *   → Storage is dramatically cheaper (P2P, no cloud overhead)
 */

import { EventEmitter } from 'events'

/**
 * Default rate card (sats per unit).
 * Relay operators can override these via config.
 */
const DEFAULT_RATES = {
  // ─── AI Services ───
  // Priced per 1K tokens (input vs output)
  'ai.infer': {
    perInputToken: 0.001, // 1 sat per 1K input tokens
    perOutputToken: 0.002, // 2 sats per 1K output tokens
    perCall: 1, // 1 sat minimum per call
    description: 'LLM inference'
  },
  'ai.embed': {
    perInputToken: 0.0005, // 0.5 sat per 1K input tokens
    perCall: 0.5, // 0.5 sat minimum per call
    description: 'Text embeddings'
  },
  'ai.list-models': {
    perCall: 0, // Free — discovery endpoint
    description: 'List available models'
  },
  'ai.status': {
    perCall: 0,
    description: 'AI service status'
  },

  // ─── Storage Services ───
  'storage.drive-create': {
    perCall: 5, // 5 sats to create a drive
    description: 'Create Hyperdrive'
  },
  'storage.drive-write': {
    perCall: 1, // 1 sat per write
    perKB: 0.01, // 0.01 sat per KB written
    description: 'Write to drive'
  },
  'storage.drive-read': {
    perCall: 0, // Reads are free (P2P replication)
    description: 'Read from drive'
  },
  'storage.drive-list': {
    perCall: 0,
    description: 'List drive contents'
  },

  // ─── Identity Services ───
  'identity.whoami': {
    perCall: 0,
    description: 'Get identity'
  },
  'identity.sign': {
    perCall: 1, // 1 sat per signature
    description: 'Cryptographic signature'
  },
  'identity.verify': {
    perCall: 0, // Verification is free
    description: 'Verify signature'
  },

  // ─── Schema Services ───
  'schema.register': {
    perCall: 2, // 2 sats to register schema
    description: 'Register data schema'
  },
  'schema.validate': {
    perCall: 0, // Validation is free
    description: 'Validate against schema'
  },
  'schema.list': {
    perCall: 0,
    description: 'List schemas'
  },

  // ─── SLA Services ───
  'sla.create': {
    perCall: 0, // SLA creation is free (collateral is separate)
    description: 'Create SLA contract'
  },
  'sla.list': {
    perCall: 0,
    description: 'List SLA contracts'
  },
  'sla.get': {
    perCall: 0,
    description: 'Get SLA details'
  }
}

export class PricingEngine extends EventEmitter {
  constructor (opts = {}) {
    super()
    this.rates = {}
    // Merge defaults with overrides
    for (const [route, defaults] of Object.entries(DEFAULT_RATES)) {
      this.rates[route] = { ...defaults, ...(opts.rates?.[route] || {}) }
    }
    // Allow adding entirely new routes
    if (opts.rates) {
      for (const [route, rate] of Object.entries(opts.rates)) {
        if (!this.rates[route]) this.rates[route] = rate
      }
    }
    // Operator margin multiplier (1.0 = no markup, 1.5 = 50% markup)
    this.margin = opts.margin || 1.0
  }

  /**
   * Calculate cost in sats for a service call.
   * Takes the route and response metadata (tokens, bytes, duration).
   *
   * @param {string} route - Service route (e.g., 'ai.infer')
   * @param {object} meta - Call metadata from service response
   * @param {number} [meta.inputTokens] - Input tokens consumed
   * @param {number} [meta.outputTokens] - Output tokens generated
   * @param {number} [meta.bytes] - Bytes written/transferred
   * @param {number} [meta.durationMs] - Reserved for future duration-based pricing
   * @returns {{ cost: number, breakdown: object }}
   */
  calculate (route, meta = {}) {
    const rate = this.rates[route]
    if (!rate) {
      // Unknown route — charge minimum 1 sat
      return { cost: 1, breakdown: { base: 1 }, route }
    }

    let cost = 0
    const breakdown = {}

    // Per-call base cost
    if (rate.perCall) {
      cost += rate.perCall
      breakdown.base = rate.perCall
    }

    // Token-based pricing (AI services)
    if (rate.perInputToken && meta.inputTokens) {
      const inputCost = meta.inputTokens * rate.perInputToken
      cost += inputCost
      breakdown.inputTokens = { count: meta.inputTokens, cost: inputCost }
    }
    if (rate.perOutputToken && meta.outputTokens) {
      const outputCost = meta.outputTokens * rate.perOutputToken
      cost += outputCost
      breakdown.outputTokens = { count: meta.outputTokens, cost: outputCost }
    }

    // Byte-based pricing (storage)
    if (rate.perKB && meta.bytes) {
      const kbCost = (meta.bytes / 1024) * rate.perKB
      cost += kbCost
      breakdown.bytes = { count: meta.bytes, cost: kbCost }
    }

    // Duration-based pricing (reserved for future use)
    if (rate.perMs && meta.durationMs) {
      const durationCost = meta.durationMs * rate.perMs
      cost += durationCost
      breakdown.duration = { ms: meta.durationMs, cost: durationCost }
    }

    // Apply operator margin
    cost *= this.margin

    // Apply cap if set
    if (rate.maxCharge && cost > rate.maxCharge) {
      cost = rate.maxCharge
      breakdown.capped = true
    }

    // Round up to nearest sat (minimum 0 for free routes)
    cost = Math.ceil(cost)

    return { cost, breakdown, route }
  }

  /**
   * Get the rate card for display / API response.
   */
  getRateCard () {
    const card = {}
    for (const [route, rate] of Object.entries(this.rates)) {
      card[route] = {
        ...rate,
        effectiveMargin: this.margin
      }
    }
    return card
  }

  /**
   * Get human-readable pricing comparison.
   * Useful for marketing / dashboard.
   */
  getComparison (btcPriceUsd = 60000) {
    const satToUsd = btcPriceUsd / 100_000_000

    return {
      btcPriceUsd,
      satToUsd,
      services: {
        'ai.infer': {
          hiverelay: {
            per1kInput: this.rates['ai.infer'].perInputToken * 1000 * this.margin,
            per1kOutput: this.rates['ai.infer'].perOutputToken * 1000 * this.margin,
            per1kInputUsd: this.rates['ai.infer'].perInputToken * 1000 * this.margin * satToUsd,
            per1kOutputUsd: this.rates['ai.infer'].perOutputToken * 1000 * this.margin * satToUsd
          },
          claude: { per1kInputUsd: 0.003, per1kOutputUsd: 0.015 },
          openai: { per1kInputUsd: 0.0025, per1kOutputUsd: 0.010 },
          savingsVsClaude: '~5x cheaper (input), ~12x cheaper (output)',
          savingsVsOpenai: '~4x cheaper (input), ~8x cheaper (output)'
        }
      }
    }
  }

  /**
   * Estimate cost for a batch of service calls (for budgeting).
   */
  estimate (calls) {
    let total = 0
    const items = []

    for (const call of calls) {
      const result = this.calculate(call.route, call.meta || {})
      total += result.cost
      items.push(result)
    }

    return { total, items }
  }

  /**
   * Check if a route is free (no charge).
   */
  isFree (route) {
    const rate = this.rates[route]
    if (!rate) return false
    return rate.perCall === 0 &&
      !rate.perInputToken &&
      !rate.perOutputToken &&
      !rate.perKB &&
      !rate.perMs
  }
}