@node-dlc/core
Version:
290 lines • 17.2 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.validateCsoPayoutFunction = exports.getCsoInfoFromOffer = exports.getCsoInfoFromContractInfo = exports.getCsoInfoParamsFromContractInfoV1 = exports.getCsoInfoParamsFromContractInfoV0 = void 0;
const bitcoin_1 = require("@node-dlc/bitcoin");
const messaging_1 = require("@node-dlc/messaging");
const assert_1 = __importDefault(require("assert"));
const decimal_js_1 = __importDefault(require("decimal.js"));
const Builder_1 = require("./Builder");
const ONE_BTC_CONTRACT = bitcoin_1.Value.fromBitcoin(1);
/**
* getCsoInfoParamsFromContractInfo V0
*
* Old getCsoInfoParamsFromContractInfo implementation
*
* @param {Value} contractSize - The size of the contract in terms of value.
* @param {Value} collateral - The collateral value put up for the contract.
* @param {DlcParty} shiftForFees - Specifies which party ('offeror' or 'acceptor') will bear the fees, affecting the outcome values.
* @param {Value} fees - The fees associated with the contract.
* @param {string} unit - The unit of measurement for the contract outcomes (e.g., 'BTC').
* @param {Value} startOutcomeValue - The starting outcome value for the contract.
* @param {Value} endOutcomeValue - The ending outcome value for the contract.
* @returns {CsoInfoParams} An object containing the calculated CSO parameters:
* - normalizedMaxGain: Maximum gain relative to a 1 BTC contract.
* - normalizedMaxLoss: Maximum loss relative to a 1 BTC contract.
* - maxGainForContractSize: Maximum gain for the actual contract size.
* - maxLossForContractSize: Maximum loss for the actual contract size.
* - offerCollateral: The offer collateral value after adjustments.
*
* Note: This function calculates the adjusted fees incorrectly, using collateral instead of contract size. Use v1 where possible.
*/
const getCsoInfoParamsFromContractInfoV0 = (contractSize, collateral, shiftForFees, fees, unit, startOutcomeValue, endOutcomeValue) => {
const leverageMultiplier = parseFloat(new decimal_js_1.default(contractSize.bitcoin).dividedBy(collateral.bitcoin).toFixed(1));
const defaultContractSize = bitcoin_1.Value.fromBitcoin(1);
const shiftValue = collateral.sats > 0
? bitcoin_1.Value.fromSats((0, Builder_1.roundToNearestMultiplier)((fees.sats * defaultContractSize.sats) / collateral.sats, // WARNING: this should be contract size not collateral
Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()]))
: bitcoin_1.Value.zero();
if (shiftForFees === 'offeror') {
startOutcomeValue.sub(shiftValue);
endOutcomeValue.sub(shiftValue);
}
else if (shiftForFees === 'acceptor') {
startOutcomeValue.add(shiftValue);
endOutcomeValue.add(shiftValue);
}
const normalizedMaxGain = endOutcomeValue.clone();
normalizedMaxGain.sub(ONE_BTC_CONTRACT);
const normalizedMaxLoss = ONE_BTC_CONTRACT.clone();
normalizedMaxLoss.sub(startOutcomeValue);
const maxGainForContractSize = bitcoin_1.Value.fromBitcoin(new decimal_js_1.default(normalizedMaxGain.bitcoin)
.times(leverageMultiplier)
.toDecimalPlaces(5)
.toNumber());
const maxLossForContractSize = bitcoin_1.Value.fromBitcoin(new decimal_js_1.default(normalizedMaxLoss.bitcoin)
.times(contractSize.bitcoin)
.toDecimalPlaces(8 - Math.log10(Number(Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()])))
.toNumber());
const offerCollateral = collateral.clone();
offerCollateral.sub(bitcoin_1.Value.fromSats((maxGainForContractSize.sats * collateral.sats) / BigInt(1e8)));
return {
normalizedMaxGain,
normalizedMaxLoss,
maxGainForContractSize,
maxLossForContractSize,
offerCollateral,
};
};
exports.getCsoInfoParamsFromContractInfoV0 = getCsoInfoParamsFromContractInfoV0;
/**
* getCsoInfoParamsFromContractInfo V1
*
* Fixed getCsoInfoParamsFromContractInfo implementation
*
* @param {Value} contractSize - The size of the contract in terms of value.
* @param {Value} collateral - The collateral value put up for the contract.
* @param {DlcParty} shiftForFees - Specifies which party ('offeror' or 'acceptor') will bear the fees, affecting the outcome values.
* @param {Value} fees - The fees associated with the contract.
* @param {string} unit - The unit of measurement for the contract outcomes (e.g., 'BTC').
* @param {Value} startOutcomeValue - The starting outcome value for the contract.
* @param {Value} endOutcomeValue - The ending outcome value for the contract.
* @returns {CsoInfoParams} An object containing the calculated CSO parameters:
* - normalizedMaxGain: Maximum gain relative to a 1 BTC contract.
* - normalizedMaxLoss: Maximum loss relative to a 1 BTC contract.
* - maxGainForContractSize: Maximum gain for the actual contract size.
* - maxLossForContractSize: Maximum loss for the actual contract size.
* - offerCollateral: The offer collateral value after adjustments.
*
* This version improves upon the previous by correctly adjusting fees based on the contract size, leading to more accurate
* calculations of CSO parameters.
*/
const getCsoInfoParamsFromContractInfoV1 = (contractSize, collateral, shiftForFees, fees, unit, startOutcomeValue, endOutcomeValue) => {
const feesAdjusted = bitcoin_1.Value.fromSats((0, Builder_1.roundToNearestMultiplier)((fees.sats * BigInt(1e8)) / contractSize.sats, // NOTE: this is done correctly using contractSize
BigInt(Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()])));
if (shiftForFees === 'offeror') {
startOutcomeValue.sub(feesAdjusted);
endOutcomeValue.sub(feesAdjusted);
}
else if (shiftForFees === 'acceptor') {
startOutcomeValue.add(feesAdjusted);
endOutcomeValue.add(feesAdjusted);
}
const normalizedMaxGain = endOutcomeValue.clone();
normalizedMaxGain.sub(ONE_BTC_CONTRACT);
const normalizedMaxLoss = ONE_BTC_CONTRACT.clone();
normalizedMaxLoss.sub(startOutcomeValue);
const maxGainForContractSize = bitcoin_1.Value.fromSats((0, Builder_1.roundUpToNearestMultiplier)((normalizedMaxGain.sats * contractSize.sats) / BigInt(1e8), BigInt(Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()])));
const maxLossForContractSize = bitcoin_1.Value.fromSats((0, Builder_1.roundUpToNearestMultiplier)((normalizedMaxLoss.sats * contractSize.sats) / BigInt(1e8), BigInt(Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()])));
const offerCollateral = collateral.clone();
offerCollateral.sub(maxGainForContractSize);
return {
normalizedMaxGain,
normalizedMaxLoss,
maxGainForContractSize,
maxLossForContractSize,
offerCollateral,
};
};
exports.getCsoInfoParamsFromContractInfoV1 = getCsoInfoParamsFromContractInfoV1;
/**
* Decode CsoInfo from a ContractInfo object. Essentially the opposite of buildCustomStrategyOrderOffer
*
* @param {_contractInfo} ContractInfo - Contract Info object, containing oracle and descriptor info
* @param {DlcParty} shiftForFees - Specifies which party ('offeror', 'acceptor', or 'neither') will pay for network fees
* @param {Value} fees - Network fees associated with the contract. Defaults to 0 sats.
* @param {_contractSize} Value - Optional. If not provided, it defaults to the total collateral.
* @param {csoVersion} 'v0' | 'v1' - Specifies the version of the CSO parameter calculation to use. Defaults to 'v1'.
* @returns {CsoInfo} An object containing the calculated CSO information:
* - normalizedMaxGain: Maximum gain relative to a 1 BTC contract.
* - normalizedMaxLoss: Maximum loss relative to a 1 BTC contract.
* - maxGainForContractSize: Maximum gain for the actual contract size.
* - maxLossForContractSize: Maximum loss for the actual contract size.
* - minPayout: Minimum payout as determined by the contract's payout function.
* - maxPayout: Maximum payout as determined by the contract's payout function.
* - contractSize: The size of the contract in terms of value.
* - offerCollateral: The offer collateral value after adjustments.
* - totalCollateral: The total collateral put up for the contract.
* - expiry: The expiry date of the contract based on the oracle's event maturity epoch.
*
* Note: This function performs several validations to ensure that the contract information and its components are of the
* expected types and formats.
* It throws errors if unsupported types or formats are encountered.
*/
const getCsoInfoFromContractInfo = (_contractInfo, shiftForFees = 'neither', fees = bitcoin_1.Value.fromSats(0), _contractSize, csoVersion = 'v1') => {
if (_contractInfo.contractInfoType !== messaging_1.ContractInfoType.Single)
throw Error('Only ContractInfoV0 currently supported');
const contractInfo = _contractInfo;
if (contractInfo.contractDescriptor.contractDescriptorType !==
messaging_1.ContractDescriptorType.NumericOutcome)
throw Error('Only Numeric Descriptor currently supported');
const oracleInfo = contractInfo.oracleInfo;
// Handle both SingleOracleInfo and MultiOracleInfo
let eventMaturityEpoch;
let eventDescriptor;
switch (oracleInfo.type) {
case messaging_1.MessageType.SingleOracleInfo: {
const singleOracleInfo = oracleInfo;
eventMaturityEpoch =
singleOracleInfo.announcement.oracleEvent.eventMaturityEpoch;
eventDescriptor = singleOracleInfo.announcement.oracleEvent
.eventDescriptor;
if (singleOracleInfo.announcement.oracleEvent.eventDescriptor.type !==
messaging_1.MessageType.DigitDecompositionEventDescriptor)
throw Error('Only DigitDecompositionEventDescriptor currently supported');
break;
}
case messaging_1.MessageType.MultiOracleInfo: {
const multiOracleInfo = oracleInfo;
eventMaturityEpoch =
multiOracleInfo.announcements[0].oracleEvent.eventMaturityEpoch;
eventDescriptor = multiOracleInfo.announcements[0].oracleEvent
.eventDescriptor;
if (multiOracleInfo.announcements[0].oracleEvent.eventDescriptor.type !==
messaging_1.MessageType.DigitDecompositionEventDescriptor)
throw Error('Only DigitDecompositionEventDescriptor currently supported');
break;
}
default:
throw Error('Unknown oracle info type');
}
const contractDescriptor = contractInfo.contractDescriptor;
if (contractDescriptor.payoutFunction.type !== messaging_1.MessageType.PayoutFunction)
throw Error('Only PayoutFunction currently supported');
const payoutFunction = contractDescriptor.payoutFunction;
(0, exports.validateCsoPayoutFunction)(payoutFunction);
const initialPiece = payoutFunction.payoutFunctionPieces[0];
const midPiece = payoutFunction.payoutFunctionPieces[1];
const minPayout = initialPiece.endPoint.outcomePayout;
const maxPayout = midPiece.endPoint.outcomePayout;
const startOutcome = initialPiece.endPoint.eventOutcome;
const endOutcome = midPiece.endPoint.eventOutcome;
const unit = eventDescriptor.unit;
const collateral = bitcoin_1.Value.fromSats(contractInfo.totalCollateral);
const contractSize = _contractSize && _contractSize.sats > 0 ? _contractSize : collateral;
const startOutcomeValue = bitcoin_1.Value.fromSats(startOutcome * Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()]);
const endOutcomeValue = bitcoin_1.Value.fromSats(endOutcome * Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()]);
const getCsoInfoParamsFromContractInfo = csoVersion === 'v0'
? exports.getCsoInfoParamsFromContractInfoV0
: exports.getCsoInfoParamsFromContractInfoV1;
const { normalizedMaxGain, normalizedMaxLoss, maxGainForContractSize, maxLossForContractSize, offerCollateral, } = getCsoInfoParamsFromContractInfo(contractSize, collateral, shiftForFees, fees, unit, startOutcomeValue, endOutcomeValue);
const expiry = new Date(eventMaturityEpoch * 1000);
return {
normalizedMaxGain,
normalizedMaxLoss,
maxGainForContractSize,
maxLossForContractSize,
minPayout,
maxPayout,
contractSize,
offerCollateral,
totalCollateral: collateral,
expiry,
};
};
exports.getCsoInfoFromContractInfo = getCsoInfoFromContractInfo;
/**
* Get CsoInfo from OrderOffer or DlcOffer and validate
*
* @param {HasContractInfo & HasType} offer
* @returns {CsoInfo}
*/
const getCsoInfoFromOffer = (offer, csoVersion = 'v1') => {
if (offer.type !== messaging_1.MessageType.DlcOffer &&
offer.type !== messaging_1.MessageType.OrderOffer)
throw Error('Only DlcOffer and OrderOffer currently supported');
let shiftForFees = 'neither';
const fees = bitcoin_1.Value.zero();
const contractSize = bitcoin_1.Value.zero();
if (offer.positionInfo) {
shiftForFees = offer.positionInfo.shiftForFees;
fees.add(bitcoin_1.Value.fromSats(offer.positionInfo.fees));
contractSize.add(bitcoin_1.Value.fromSats(offer.positionInfo.contractSize));
}
const positionInfo = (0, exports.getCsoInfoFromContractInfo)(offer.contractInfo, shiftForFees, fees, contractSize, csoVersion);
if (positionInfo.offerCollateral.sats !== offer.offerCollateral)
throw Error('Offer was not generated with CSO ContractInfo');
return positionInfo;
};
exports.getCsoInfoFromOffer = getCsoInfoFromOffer;
/**
* Validate Payout Function for proper CSO format
*
* It should have 3 PayoutCurvePieces which consist of a flat line (maxLoss),
* ascending line (maxLoss to maxGain) and finally another flat line (maxGain)
*
* All PayoutCurvePieces should be type PolynomialPayoutCurvePieces
*
* @param {PayoutFunction} payoutFunction
*/
const validateCsoPayoutFunction = (payoutFunction) => {
(0, assert_1.default)(payoutFunction.payoutFunctionPieces.length === 3, 'CSO Payout Function must have 3 PayoutFunctionPieces');
for (const [i, piece] of payoutFunction.payoutFunctionPieces.entries()) {
(0, assert_1.default)(piece.payoutCurvePiece.payoutCurvePieceType ===
messaging_1.PayoutCurvePieceType.Polynomial ||
piece.payoutCurvePiece.type === messaging_1.MessageType.PolynomialPayoutCurvePiece, 'CSO Payout Function PayoutCurvePieces must be PolynomialCurvePieces');
const payoutCurvePiece = piece.payoutCurvePiece;
const points = payoutCurvePiece.points;
// eventOutcome should always be ascending
(0, assert_1.default)(points[0].eventOutcome < points[1].eventOutcome, 'CSO Payout Function PayoutCurvePiece point payout should be an ascending line');
// endpoints should always be ascending
let previousPiece, previousPoints;
if (i > 0) {
previousPiece = payoutFunction.payoutFunctionPieces[i - 1];
previousPoints = previousPiece.payoutCurvePiece.points;
(0, assert_1.default)(previousPiece.endPoint.eventOutcome < piece.endPoint.eventOutcome, 'CSO Payout Function point endpoints should be an ascending line');
(0, assert_1.default)(previousPoints[1].outcomePayout === points[0].outcomePayout, 'CSO Payout Function point outcome payout should be continuous without gaps');
}
switch (i) {
case 0:
// First piece - should start from initial endpoint
// maxLoss should be a flat line
(0, assert_1.default)(points[0].outcomePayout === points[1].outcomePayout, 'CSO Payout Function maxLoss PayoutCurvePiece point should be a flat line');
break;
case 1:
// maxLoss to maxGain should be an ascending line
(0, assert_1.default)(previousPiece.endPoint.outcomePayout < piece.endPoint.outcomePayout);
(0, assert_1.default)(points[0].outcomePayout < points[1].outcomePayout, 'CSO Payout Function maxLoss to maxGain PayoutCurvePiece point should be an ascending line');
break;
case 2:
// maxGain should be a flat line
(0, assert_1.default)(previousPiece.endPoint.outcomePayout === piece.endPoint.outcomePayout);
(0, assert_1.default)(points[0].outcomePayout === points[1].outcomePayout, 'CSO Payout Function maxGain PayoutCurvePiece point should be a flat line');
break;
}
}
};
exports.validateCsoPayoutFunction = validateCsoPayoutFunction;
//# sourceMappingURL=CsoInfo.js.map