near-toll-audit / docs/model.js · raw
// Valuation engine used by the live page. pipeline/model.py is the same maths; tests/test_model.py proves they agree.(function (root) {  "use strict";  // Linear path from a start value in year 1 to an end value in year 5.  function path(a, b, t) { return a + (b - a) * (Math.min(t, 5) - 1) / 4; }  function issuanceRate(p, t) {    var ramp = Math.max(1, p.ramp_years);    return p.issuance_start + (p.issuance_end - p.issuance_start) * Math.min(t, ramp) / ramp;  }  function value(p) {    var ownership = 1, pv = 0, rows = [], earnings = 0, vol = p.vol0;    for (var t = 1; t <= 5; t++) {      vol = vol * (1 + p.cagr);      var gross = vol * path(p.gross_bps_start, p.gross_bps_end, t) / 1e4;      var captured = gross * path(p.capture_start, p.capture_end, t);      var l1 = p.l1_burn_usd * Math.pow(1 + p.l1_growth, t);      earnings = captured * p.claim + l1;      // Staking rewards are a wash for a holder who stakes. Only the treasury share and commission leave the holder base.      var leak = issuanceRate(p, t) * (p.treasury_share + (1 - p.treasury_share) * p.commission);      ownership *= 1 - leak;      pv += earnings * ownership / Math.pow(1 + p.discount, t);      rows.push({ year: t, volume: vol, gross: gross, captured: captured, earnings: earnings, ownership: ownership });    }    var terminal = earnings * p.exit_multiple * ownership / Math.pow(1 + p.discount, 5);    var total = pv + terminal + p.l1_value;    return {      price: total / p.supply, equity: total, pv_cash: pv, pv_terminal: terminal, l1_value: p.l1_value,      y5_volume: vol, y5_captured: rows[4].captured, y5_earnings: earnings, rows: rows    };  }  // Year-5 volume at which the model equals a target price, all else held.  function impliedVolume(p, target) {    var lo = -0.95, hi = 20, q = Object.assign({}, p);    function at(g) { q.cagr = g; return value(q).price; }    if (at(hi) < target) return null;    if (at(lo) > target) return { cagr: lo, y5_volume: p.vol0 * Math.pow(1 + lo, 5) };    for (var i = 0; i < 80; i++) {      var mid = (lo + hi) / 2;      if (at(mid) < target) lo = mid; else hi = mid;    }    var g = (lo + hi) / 2;    return { cagr: g, y5_volume: p.vol0 * Math.pow(1 + g, 5) };  }  function weighted(scenarios) {    var out = 0;    Object.keys(scenarios).forEach(function (k) { out += scenarios[k].probability * value(scenarios[k].inputs).price; });    return out;  }  // Probability on the bull case that makes the weighted value equal the market price,  // holding the bear to base ratio fixed.  function impliedBull(scenarios, target) {    var bear = value(scenarios.bear.inputs).price, base = value(scenarios.base.inputs).price, bull = value(scenarios.bull.inputs).price;    var a = scenarios.bear.probability, b = scenarios.base.probability;    var rest = (a * bear + b * base) / (a + b);    if (bull === rest) return null;    return (target - rest) / (bull - rest);  }  var api = { value: value, impliedVolume: impliedVolume, weighted: weighted, impliedBull: impliedBull, issuanceRate: issuanceRate };  if (typeof module !== "undefined" && module.exports) module.exports = api;  else root.NearModel = api;})(typeof window !== "undefined" ? window : this);