UNPKG

@node-dlc/messaging

Version:
455 lines (397 loc) 14.3 kB
import { BufferReader, BufferWriter } from '@node-dlc/bufio'; import { MessageType, PayoutCurvePieceType } from '../MessageType'; import { F64 } from '../serialize/F64'; import { bigIntToNumber, toBigInt } from '../util'; import { IDlcMessage } from './DlcMessage'; export abstract class PayoutCurvePiece { public static deserialize( buf: Buffer, ): PolynomialPayoutCurvePiece | HyperbolaPayoutCurvePiece { const reader = new BufferReader(buf); const typeId = Number(reader.readBigSize()); switch (typeId) { case PayoutCurvePieceType.Polynomial: return PolynomialPayoutCurvePiece.deserialize(buf); case PayoutCurvePieceType.Hyperbola: return HyperbolaPayoutCurvePiece.deserialize(buf); default: throw new Error( `Payout curve piece type must be Polynomial (0) or Hyperbola (1), got ${typeId}`, ); } } /** * Creates a PayoutCurvePiece from JSON data * @param json JSON object representing a payout curve piece */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any public static fromJSON(json: any): PayoutCurvePiece { if (!json) { throw new Error('payoutCurvePiece is required'); } // Handle test vector format with nested types if (json.polynomialPayoutCurvePiece) { return PolynomialPayoutCurvePiece.fromJSON( json.polynomialPayoutCurvePiece, ); } else if (json.hyperbolaPayoutCurvePiece) { return HyperbolaPayoutCurvePiece.fromJSON(json.hyperbolaPayoutCurvePiece); } // Handle direct format else if (json.points !== undefined || json.payoutPoints !== undefined) { return PolynomialPayoutCurvePiece.fromJSON(json); } else if (json.usePositivePiece !== undefined) { return HyperbolaPayoutCurvePiece.fromJSON(json); } else { throw new Error( 'payoutCurvePiece must be either polynomial (with points) or hyperbola (with usePositivePiece)', ); } } public abstract payoutCurvePieceType: PayoutCurvePieceType; public abstract type: number; // For backward compatibility public abstract toJSON(): | PolynomialPayoutCurvePieceJSON | HyperbolaPayoutCurvePieceJSON; public abstract serialize(): Buffer; } /** * PolynomialPayoutCurvePiece defines a polynomial curve piece for payout functions. * This corresponds to type 0 in the sibling sub-type format. */ export class PolynomialPayoutCurvePiece extends PayoutCurvePiece implements IDlcMessage { public static payoutCurvePieceType = PayoutCurvePieceType.Polynomial; /** * Creates a PolynomialPayoutCurvePiece from JSON data * @param json JSON object representing a polynomial payout curve piece */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any public static fromJSON(json: any): PolynomialPayoutCurvePiece { const instance = new PolynomialPayoutCurvePiece(); const points = json.payoutPoints || json.points || []; // eslint-disable-next-line @typescript-eslint/no-explicit-any instance.points = points.map((point: any) => ({ eventOutcome: toBigInt(point.eventOutcome || point.event_outcome), outcomePayout: toBigInt(point.outcomePayout || point.outcome_payout), extraPrecision: point.extraPrecision || point.extra_precision || 0, })); return instance; } /** * Deserializes a polynomial_payout_curve_piece message * @param buf */ public static deserialize(buf: Buffer): PolynomialPayoutCurvePiece { const instance = new PolynomialPayoutCurvePiece(); const reader = new BufferReader(buf); reader.readBigSize(); // read type (0) const numPts = Number(reader.readBigSize()); for (let i = 0; i < numPts; i++) { const eventOutcome = reader.readUInt64BE(); const outcomePayout = reader.readUInt64BE(); const extraPrecision = reader.readUInt16BE(); instance.points.push({ eventOutcome, outcomePayout, extraPrecision, }); } return instance; } /** * The type for polynomial_payout_curve_piece message - Note: this is a sub-component, not a standalone wire message */ public type = MessageType.PolynomialPayoutCurvePiece; /** * The payout curve piece type for new format */ public payoutCurvePieceType = PayoutCurvePieceType.Polynomial; public points: IPoint[] = []; /** * Converts polynomial_payout_curve_piece to JSON */ public toJSON(): PolynomialPayoutCurvePieceJSON { return { polynomialPayoutCurvePiece: { payoutPoints: this.points.map((point) => { return { eventOutcome: bigIntToNumber(point.eventOutcome), outcomePayout: bigIntToNumber(point.outcomePayout), extraPrecision: Number(point.extraPrecision), }; }), }, }; } /** * Serializes the polynomial_payout_curve_piece message into a Buffer */ public serialize(): Buffer { const writer = new BufferWriter(); writer.writeBigSize(this.payoutCurvePieceType); writer.writeBigSize(this.points.length); for (const point of this.points) { writer.writeUInt64BE(point.eventOutcome); writer.writeUInt64BE(point.outcomePayout); writer.writeUInt16BE(point.extraPrecision); } return writer.toBuffer(); } } /** * HyperbolaPayoutCurvePiece defines a hyperbola curve piece for payout functions. * This corresponds to type 1 in the sibling sub-type format. * Updated to use F64 for precise f64 parameter handling. */ export class HyperbolaPayoutCurvePiece extends PayoutCurvePiece implements IDlcMessage { public static payoutCurvePieceType = PayoutCurvePieceType.Hyperbola; /** * Helper function to safely parse F64 values from JSON * Handles both number and string inputs for maximum precision */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any private static parseF64Value(value: any): F64 | null { // Check for basic null/undefined if (value === null || value === undefined) { return null; } try { if (typeof value === 'string') { // Parse string directly to preserve precision try { return F64.fromString(value); } catch (error) { // If fromString fails, try parsing as number const numValue = parseFloat(value); if (!isNaN(numValue) && isFinite(numValue)) { return F64.fromNumber(numValue); } } } else if (typeof value === 'number') { // Parse number - handle special cases if (!isFinite(value)) { return null; // Reject NaN, Infinity, -Infinity } return F64.fromNumber(value); } // Try to convert other types to number as fallback const numValue = Number(value); if (!isNaN(numValue) && isFinite(numValue)) { return F64.fromNumber(numValue); } return null; } catch (error) { return null; } } /** * Creates a HyperbolaPayoutCurvePiece from JSON data * @param json JSON object representing a hyperbola payout curve piece */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any public static fromJSON(json: any): HyperbolaPayoutCurvePiece | null { if (!json || typeof json !== 'object') return null; // Handle both wrapped format and direct format let data = json.hyperbolaPayoutCurvePiece || json.hyperbola_payout_curve_piece; // If no wrapper found, assume direct format if it has the expected properties if ( !data && (json.usePositivePiece !== undefined || json.use_positive_piece !== undefined) ) { data = json; } if (!data) return null; try { const usePositivePiece = data.usePositivePiece || data.use_positive_piece || false; // Parse each F64 value with null check const translateOutcome = this.parseF64Value( data.translateOutcome !== undefined ? data.translateOutcome : data.translate_outcome, ); const translatePayout = this.parseF64Value( data.translatePayout !== undefined ? data.translatePayout : data.translate_payout, ); const a = this.parseF64Value(data.a); const b = this.parseF64Value(data.b); const c = this.parseF64Value(data.c); const d = this.parseF64Value(data.d); // Check that all required values were parsed successfully if (!translateOutcome || !translatePayout || !a || !b || !c || !d) { throw new Error('Failed to parse one or more F64 values'); } return new HyperbolaPayoutCurvePiece( usePositivePiece, translateOutcome, translatePayout, a, b, c, d, ); } catch (error) { return null; } } /** * Deserializes a hyperbola_payout_curve_piece message * @param buf */ public static deserialize(buf: Buffer): HyperbolaPayoutCurvePiece { const instance = new HyperbolaPayoutCurvePiece(); const reader = new BufferReader(buf); reader.readBigSize(); // read type (1) instance.usePositivePiece = reader.readUInt8() === 1; // Read f64 values using F64 for precise handling - read raw 8-byte buffers instance.translateOutcome = F64.deserialize(reader.readBytes(8)); instance.translatePayout = F64.deserialize(reader.readBytes(8)); instance.a = F64.deserialize(reader.readBytes(8)); instance.b = F64.deserialize(reader.readBytes(8)); instance.c = F64.deserialize(reader.readBytes(8)); instance.d = F64.deserialize(reader.readBytes(8)); // Note: leftEndPoint and rightEndPoint are not part of the serialization // They will be set by PayoutFunction when creating from JSON instance.leftEndPoint = { eventOutcome: BigInt(0), outcomePayout: BigInt(0), extraPrecision: 0, }; instance.rightEndPoint = { eventOutcome: BigInt(0), outcomePayout: BigInt(0), extraPrecision: 0, }; return instance; } /** * The type for hyperbola_payout_curve_piece message - Note: this is a sub-component, not a standalone wire message */ public type = MessageType.HyperbolaPayoutCurvePiece; public payoutCurvePieceType = HyperbolaPayoutCurvePiece.payoutCurvePieceType; // Use F64 for precise f64 parameter handling to avoid JavaScript precision issues public leftEndPoint: IPayoutPoint; public rightEndPoint: IPayoutPoint; public usePositivePiece: boolean; public translateOutcome: F64; // f64 - precise handling with F64 public translatePayout: F64; // f64 - precise handling with F64 public a: F64; // f64 - precise handling with F64 public b: F64; // f64 - precise handling with F64 public c: F64; // f64 - precise handling with F64 public d: F64; // f64 - precise handling with F64 constructor( usePositivePiece = false, translateOutcome?: string | F64, translatePayout?: string | F64, a?: string | F64, b?: string | F64, c?: string | F64, d?: string | F64, ) { super(); this.usePositivePiece = usePositivePiece; // Convert string inputs to F64 objects, or use existing F64 objects this.translateOutcome = translateOutcome ? typeof translateOutcome === 'string' ? F64.fromString(translateOutcome) : translateOutcome : F64.fromNumber(0); this.translatePayout = translatePayout ? typeof translatePayout === 'string' ? F64.fromString(translatePayout) : translatePayout : F64.fromNumber(0); this.a = a ? typeof a === 'string' ? F64.fromString(a) : a : F64.fromNumber(0); this.b = b ? typeof b === 'string' ? F64.fromString(b) : b : F64.fromNumber(0); this.c = c ? typeof c === 'string' ? F64.fromString(c) : c : F64.fromNumber(0); this.d = d ? typeof d === 'string' ? F64.fromString(d) : d : F64.fromNumber(0); } /** * Converts hyperbola_payout_curve_piece to JSON * Uses F64.toJSONValue() which preserves precision by using strings for very large numbers */ public toJSON(): HyperbolaPayoutCurvePieceJSON { return { hyperbolaPayoutCurvePiece: { usePositivePiece: this.usePositivePiece, translateOutcome: this.translateOutcome.toJSONValue(), // Smart conversion: number if safe, string if large translatePayout: this.translatePayout.toJSONValue(), // Preserves precision automatically a: this.a.toJSONValue(), b: this.b.toJSONValue(), c: this.c.toJSONValue(), d: this.d.toJSONValue(), }, }; } /** * Serializes the hyperbola_payout_curve_piece message into a Buffer */ public serialize(): Buffer { const writer = new BufferWriter(); writer.writeBigSize(this.payoutCurvePieceType); writer.writeUInt8(this.usePositivePiece ? 1 : 0); // Write f64 values using F64 for precise handling - write raw 8-byte buffers writer.writeBytes(this.translateOutcome.serialize()); writer.writeBytes(this.translatePayout.serialize()); writer.writeBytes(this.a.serialize()); writer.writeBytes(this.b.serialize()); writer.writeBytes(this.c.serialize()); writer.writeBytes(this.d.serialize()); return writer.toBuffer(); } } interface IPoint { eventOutcome: bigint; outcomePayout: bigint; extraPrecision: number; } interface IPointJSON { eventOutcome: number; outcomePayout: number; extraPrecision: number; } interface IPayoutPoint { eventOutcome: bigint; outcomePayout: bigint; extraPrecision: number; } export interface PolynomialPayoutCurvePieceJSON { polynomialPayoutCurvePiece: { payoutPoints: IPointJSON[]; }; } export interface HyperbolaPayoutCurvePieceJSON { hyperbolaPayoutCurvePiece: { usePositivePiece: boolean; translateOutcome: number | string; translatePayout: number | string; a: number | string; b: number | string; c: number | string; d: number | string; }; }