@node-dlc/messaging
Version:
DLC Messaging Protocol
455 lines (397 loc) • 14.3 kB
text/typescript
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;
};
}