UNPKG

@node-dlc/core

Version:
290 lines 17.2 kB
"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