@whworjs7946/clmsr-v0
Version:
TypeScript SDK for CLMSR market calculations and utilities
325 lines (324 loc) • 11.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_FACTOR = exports.MIN_FACTOR = exports.MAX_CHUNKS_PER_TX = exports.MAX_EXP_INPUT_WAD = exports.USDC_PRECISION = exports.SCALE_DIFF = exports.WAD = void 0;
exports.toWad = toWad;
exports.fromWad = fromWad;
exports.fromWadRoundUp = fromWadRoundUp;
exports.wadToNumber = wadToNumber;
exports.formatUSDC = formatUSDC;
exports.wMul = wMul;
exports.wDiv = wDiv;
exports.wExp = wExp;
exports.wLn = wLn;
exports.wSqrt = wSqrt;
exports.sumExp = sumExp;
exports.logSumExp = logSumExp;
exports.clmsrPrice = clmsrPrice;
exports.clmsrCost = clmsrCost;
exports.clmsrProceeds = clmsrProceeds;
exports.safeExp = safeExp;
exports.isFactorSafe = isFactorSafe;
exports.toBig = toBig;
exports.toWAD = toWAD;
exports.toMicroUSDC = toMicroUSDC;
const big_js_1 = __importDefault(require("big.js"));
const types_1 = require("../types");
// ============================================================================
// CONSTANTS
// ============================================================================
/** WAD format constant: 1e18 */
exports.WAD = new big_js_1.default("1e18");
/** Scale difference between USDC (6 decimals) and WAD (18 decimals): 1e12 */
exports.SCALE_DIFF = new big_js_1.default("1e12");
/** USDC precision constant: 1e6 */
exports.USDC_PRECISION = new big_js_1.default("1000000");
/** Maximum safe input for exp() function: 0.13 * 1e18 */
exports.MAX_EXP_INPUT_WAD = new big_js_1.default("130000000000000000"); // 0.13 * 1e18
/** Maximum number of chunks per transaction */
exports.MAX_CHUNKS_PER_TX = 1000;
/** Minimum and maximum factor bounds for segment tree operations */
exports.MIN_FACTOR = new big_js_1.default("0.01e18"); // 1%
exports.MAX_FACTOR = new big_js_1.default("100e18"); // 100x
// Big.js configuration for precision (optimized for performance)
big_js_1.default.DP = 30; // 30 decimal places for internal calculations (sufficient for CLMSR precision)
big_js_1.default.RM = big_js_1.default.roundHalfUp; // Round half up
// ============================================================================
// SCALING FUNCTIONS
// ============================================================================
/**
* Convert 6-decimal USDC amount to 18-decimal WAD format
* @param amt6 Amount in 6-decimal format
* @returns Amount in WAD format
*/
function toWad(amt6) {
return amt6.mul(exports.SCALE_DIFF);
}
/**
* Convert 18-decimal WAD format to 6-decimal USDC amount (truncates)
* @param amtWad Amount in WAD format
* @returns Amount in 6-decimal format
*/
function fromWad(amtWad) {
return amtWad.div(exports.SCALE_DIFF);
}
/**
* Convert 18-decimal WAD format to 6-decimal USDC amount with round-up
* Always rounds up to ensure minimum 1 micro unit cost
* @param amtWad Amount in WAD format
* @returns Amount in 6-decimal format (rounded up)
*/
function fromWadRoundUp(amtWad) {
const result = amtWad.plus(exports.SCALE_DIFF.minus(1)).div(exports.SCALE_DIFF);
return new big_js_1.default(result.toFixed(6, big_js_1.default.roundUp));
}
/**
* Convert WAD format to regular number (divide by 1e18)
* @param amtWad Amount in WAD format
* @returns Regular number
*/
function wadToNumber(amtWad) {
return amtWad.div(exports.WAD);
}
/**
* Format USDC amount to 6 decimal places maximum
* @param amount USDC amount (in micro USDC)
* @returns Formatted amount with max 6 decimals
*/
function formatUSDC(amount) {
// amount는 이미 micro USDC 단위이므로 정수여야 함
return new big_js_1.default(amount.toFixed(0, big_js_1.default.roundDown));
}
// ============================================================================
// BASIC MATH OPERATIONS
// ============================================================================
/**
* WAD multiplication: (a * b) / WAD
* @param a First operand
* @param b Second operand
* @returns Product in WAD format
*/
function wMul(a, b) {
return a.mul(b).div(exports.WAD);
}
/**
* WAD division: (a * WAD) / b
* @param a Dividend
* @param b Divisor
* @returns Quotient in WAD format
*/
function wDiv(a, b) {
if (b.eq(0)) {
throw new types_1.ValidationError("Division by zero");
}
return a.mul(exports.WAD).div(b);
}
/**
* WAD exponentiation: e^x
* Uses Taylor series expansion for accurate results
* @param x Exponent in WAD format
* @returns e^x in WAD format
*/
function wExp(x) {
if (x.gt(exports.MAX_EXP_INPUT_WAD)) {
throw new types_1.ValidationError(`Exponent too large: ${x.toString()}, max: ${exports.MAX_EXP_INPUT_WAD.toString()}`);
}
// Convert to regular number for Math.exp, then back to Big
// For high precision, we could implement Taylor series, but Math.exp is sufficient for our use case
const xNumber = parseFloat(x.div(exports.WAD).toString());
const result = Math.exp(xNumber);
return new big_js_1.default(result.toString()).mul(exports.WAD);
}
/**
* WAD natural logarithm: ln(x)
* @param x Input in WAD format (must be > 0)
* @returns ln(x) in WAD format
*/
function wLn(x) {
if (x.lte(0)) {
throw new types_1.ValidationError("Logarithm input must be positive");
}
// Convert to regular number for Math.log, then back to Big
const xNumber = parseFloat(x.div(exports.WAD).toString());
const result = Math.log(xNumber);
return new big_js_1.default(result.toString()).mul(exports.WAD);
}
/**
* WAD square root: √x
* @param x Input in WAD format
* @returns √x in WAD format
*/
function wSqrt(x) {
if (x.lt(0)) {
throw new types_1.ValidationError("Square root input must be non-negative");
}
// Use Big.js sqrt method
const xScaled = x.div(exports.WAD);
const result = xScaled.sqrt();
return result.mul(exports.WAD);
}
// ============================================================================
// AGGREGATION FUNCTIONS
// ============================================================================
/**
* Sum of exponentials: Σ exp(v_i)
* @param values Array of values in WAD format
* @returns Sum of exponentials in WAD format
*/
function sumExp(values) {
if (values.length === 0) {
throw new types_1.ValidationError("Empty array provided to sumExp");
}
let sum = new big_js_1.default(0);
for (const v of values) {
const expV = wExp(v);
sum = sum.plus(expV);
}
return sum;
}
/**
* Logarithm of sum of exponentials: ln(Σ exp(v_i))
* Uses numerical stability techniques (subtract max value)
* @param values Array of values in WAD format
* @returns ln(Σ exp(v_i)) in WAD format
*/
function logSumExp(values) {
if (values.length === 0) {
throw new types_1.ValidationError("Empty array provided to logSumExp");
}
// Find maximum value for numerical stability
let maxVal = values[0];
for (let i = 1; i < values.length; i++) {
if (values[i].gt(maxVal)) {
maxVal = values[i];
}
}
// Calculate sum of exp(x - max) with proper scaling
let sumScaled = new big_js_1.default(0);
for (const v of values) {
// Safe subtraction to avoid underflow
const diff = v.gte(maxVal) ? v.minus(maxVal) : new big_js_1.default(0);
const eScaled = wExp(diff);
sumScaled = sumScaled.plus(eScaled);
}
if (sumScaled.eq(0)) {
throw new types_1.CalculationError("Sum scaled to zero in logSumExp");
}
return maxVal.plus(wLn(sumScaled));
}
// ============================================================================
// CLMSR-SPECIFIC FUNCTIONS
// ============================================================================
/**
* Calculate CLMSR price from exponential values
* Price = exp(q/α) / Σ exp(q_i/α)
* @param expValue Pre-computed exp(q/α) value for this tick
* @param totalSumExp Sum of all exponentials Σ exp(q/α)
* @returns Normalized price in WAD format
*/
function clmsrPrice(expValue, totalSumExp) {
if (totalSumExp.eq(0)) {
throw new types_1.ValidationError("Total sum of exponentials is zero");
}
return wDiv(expValue, totalSumExp);
}
/**
* Calculate CLMSR cost: α * ln(Σ_after / Σ_before)
* @param alpha Liquidity parameter α in WAD format
* @param sumBefore Sum of exponentials before trade
* @param sumAfter Sum of exponentials after trade
* @returns Trade cost in WAD format (always positive)
*/
function clmsrCost(alpha, sumBefore, sumAfter) {
if (sumBefore.eq(0)) {
throw new types_1.ValidationError("Sum before trade is zero");
}
const ratio = wDiv(sumAfter, sumBefore);
if (ratio.lt(exports.WAD)) {
throw new types_1.ValidationError("Ratio < 1 not supported in unsigned version");
}
const lnRatio = wLn(ratio);
return wMul(alpha, lnRatio);
}
/**
* Calculate CLMSR proceeds (for selling): α * ln(Σ_before / Σ_after)
* @param alpha Liquidity parameter α in WAD format
* @param sumBefore Sum of exponentials before sell
* @param sumAfter Sum of exponentials after sell
* @returns Trade proceeds in WAD format
*/
function clmsrProceeds(alpha, sumBefore, sumAfter) {
if (sumBefore.eq(0) || sumAfter.eq(0)) {
throw new types_1.ValidationError("Sum before or after trade is zero");
}
if (sumBefore.lte(sumAfter)) {
return new big_js_1.default(0); // No proceeds if sum doesn't decrease
}
const ratio = wDiv(sumBefore, sumAfter);
const lnRatio = wLn(ratio);
return wMul(alpha, lnRatio);
}
// ============================================================================
// SAFE EXPONENTIAL WITH CHUNKING
// ============================================================================
/**
* Calculate exp(q/α) safely by chunking large values to avoid overflow
* Equivalent to contract's _safeExp function
* @param q Quantity in WAD format
* @param alpha Liquidity parameter in WAD format
* @returns Result of exp(q/α) in WAD format
*/
function safeExp(q, alpha) {
if (alpha.eq(0)) {
throw new types_1.ValidationError("Alpha cannot be zero");
}
const maxPerChunk = wMul(alpha, exports.MAX_EXP_INPUT_WAD); // α * 0.13
let result = exports.WAD; // 1.0
let remaining = new big_js_1.default(q.toString());
while (remaining.gt(0)) {
const chunk = remaining.gt(maxPerChunk) ? maxPerChunk : remaining;
const factor = wExp(wDiv(chunk, alpha)); // Safe: chunk/α ≤ 0.13
result = wMul(result, factor);
remaining = remaining.minus(chunk);
}
return result;
}
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Check if a factor is within safe bounds for segment tree operations
* @param factor Factor to check
* @returns true if factor is within bounds
*/
function isFactorSafe(factor) {
return factor.gte(exports.MIN_FACTOR) && factor.lte(exports.MAX_FACTOR);
}
/**
* Create a new Big number from string, number, or Big
* @param value Input value
* @returns Big number
*/
function toBig(value) {
return new big_js_1.default(value);
}
/**
* Create WAD amount from numeric value (multiply by 1e18)
* Use this for converting regular numbers to WAD format
* @param value Input value in regular units (e.g., 1.5 USDC)
* @returns WAD amount (18 decimals)
*/
function toWAD(value) {
return new big_js_1.default(value).mul(exports.WAD);
}
/**
* Create micro-USDC amount from USDC value (multiply by 1e6)
* Use this for converting user input USDC amounts to SDK format
* @param value Input value in USDC (e.g., "100" = 100 USDC)
* @returns USDC amount in 6-decimal format (micro-USDC)
*/
function toMicroUSDC(value) {
return new big_js_1.default(value).mul(exports.USDC_PRECISION);
}