UNPKG

@node-dlc/messaging

Version:
527 lines (439 loc) 16.8 kB
import { BufferReader, BufferWriter } from '@node-dlc/bufio'; import { MessageType } from '../MessageType'; import { getTlv } from '../serialize/getTlv'; import { IDlcMessage } from './DlcMessage'; import { OracleAnnouncement, OracleAnnouncementJSON, } from './OracleAnnouncement'; /** * OracleParams describe allowed differences between oracles in * numerical outcome contracts, as per rust-dlc specification. */ export class OracleParams implements IDlcMessage { public static type = MessageType.OracleParamsV0; /** * Creates an OracleParams from JSON data * @param json JSON object representing oracle params */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any public static fromJSON(json: any): OracleParams { const instance = new OracleParams(); instance.maxErrorExp = json.maxErrorExp || json.max_error_exp || 0; instance.minFailExp = json.minFailExp || json.min_fail_exp || 0; instance.maximizeCoverage = json.maximizeCoverage || json.maximize_coverage || false; return instance; } /** * Deserializes oracle_params_v0 message */ public static deserialize(buf: Buffer): OracleParams { const instance = new OracleParams(); const reader = new BufferReader(buf); reader.readBigSize(); // read type instance.length = reader.readBigSize(); instance.maxErrorExp = reader.readUInt16BE(); instance.minFailExp = reader.readUInt16BE(); instance.maximizeCoverage = reader.readUInt8() === 1; return instance; } /** * Deserializes oracle params body without TLV wrapper (for optional deserialization) */ public static deserializeBody(buf: Buffer): OracleParams { const instance = new OracleParams(); const reader = new BufferReader(buf); // No type/length to read - just the body content instance.maxErrorExp = reader.readUInt16BE(); instance.minFailExp = reader.readUInt16BE(); instance.maximizeCoverage = reader.readUInt8() === 1; return instance; } public type = OracleParams.type; public length: bigint; /** The maximum allowed difference between oracle expressed as a power of 2. */ public maxErrorExp: number; /** The minimum allowed difference that should be supported by the contract expressed as a power of 2. */ public minFailExp: number; /** Whether to maximize coverage of the interval between maxErrorExp and minFailExp. */ public maximizeCoverage: boolean; public validate(): void { if (this.maxErrorExp < 0) { throw new Error('maxErrorExp must be greater than or equal to 0'); } if (this.minFailExp < 0) { throw new Error('minFailExp must be greater than or equal to 0'); } if (this.maxErrorExp >= this.minFailExp) { throw new Error('maxErrorExp must be less than minFailExp'); } } public toJSON(): OracleParamsJSON { return { maxErrorExp: this.maxErrorExp, minFailExp: this.minFailExp, maximizeCoverage: this.maximizeCoverage, }; } public serialize(): Buffer { const writer = new BufferWriter(); writer.writeBigSize(this.type); const dataWriter = new BufferWriter(); dataWriter.writeUInt16BE(this.maxErrorExp); dataWriter.writeUInt16BE(this.minFailExp); dataWriter.writeUInt8(this.maximizeCoverage ? 1 : 0); writer.writeBigSize(dataWriter.size); writer.writeBytes(dataWriter.toBuffer()); return writer.toBuffer(); } /** * Serializes the oracle params body without TLV wrapper (for optional serialization) */ public serializeBody(): Buffer { const writer = new BufferWriter(); writer.writeUInt16BE(this.maxErrorExp); writer.writeUInt16BE(this.minFailExp); writer.writeUInt8(this.maximizeCoverage ? 1 : 0); return writer.toBuffer(); } } /** * SingleOracleInfo contains information about a single oracle. */ export class SingleOracleInfo implements IDlcMessage { public static type = MessageType.SingleOracleInfo; /** * Creates a SingleOracleInfo from JSON data * @param json JSON object representing single oracle info */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any public static fromJSON(json: any): SingleOracleInfo { const instance = new SingleOracleInfo(); const announcementData = json.announcement || json.oracleAnnouncement; if (!announcementData) { throw new Error( 'announcement or oracleAnnouncement is required for single oracle info', ); } // Parse announcement using proper fromJSON method instance.announcement = OracleAnnouncement.fromJSON(announcementData); return instance; } /** * Deserializes single oracle info */ public static deserialize(buf: Buffer): SingleOracleInfo { const instance = new SingleOracleInfo(); const reader = new BufferReader(buf); // Read the type and length that serialize() writes reader.readBigSize(); // read type instance.length = reader.readBigSize(); // read length // Read the announcement data instance.announcement = OracleAnnouncement.deserialize( reader.readBytes(Number(instance.length)), ); return instance; } public type = SingleOracleInfo.type; public length: bigint; /** The oracle announcement from the oracle. */ public announcement: OracleAnnouncement; /** * Returns the closest maturity date amongst all events */ public getClosestMaturityDate(): number { return this.announcement.oracleEvent.eventMaturityEpoch; } public validate(): void { this.announcement.validate(); } public toJSON(): SingleOracleInfoJSON { // Return enum variant format for Rust compatibility return { single: { oracleAnnouncement: this.announcement.toJSON(), }, }; } public serialize(): Buffer { const writer = new BufferWriter(); writer.writeBigSize(this.type); const dataWriter = new BufferWriter(); dataWriter.writeBytes(this.announcement.serialize()); writer.writeBigSize(dataWriter.size); writer.writeBytes(dataWriter.toBuffer()); return writer.toBuffer(); } /** * Serializes the body without TLV wrapper (for embedding in ContractInfo) * This matches rust-dlc behavior where OracleInfo.write() doesn't add type_id */ public serializeBody(): Buffer { return this.announcement.serialize(); } /** * Deserializes the body without TLV wrapper (for embedding in ContractInfo) * This matches rust-dlc behavior where OracleInfo is read without type_id */ public static deserializeBody(buf: Buffer): SingleOracleInfo { const instance = new SingleOracleInfo(); // No type/length to read - just the announcement data directly instance.announcement = OracleAnnouncement.deserialize(buf); return instance; } } /** * MultiOracleInfo contains information about multiple oracles used in multi-oracle based contracts. */ export class MultiOracleInfo implements IDlcMessage { public static type = MessageType.MultiOracleInfo; /** * Creates a MultiOracleInfo from JSON data * @param json JSON object representing multi oracle info */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any public static fromJSON(json: any): MultiOracleInfo { const instance = new MultiOracleInfo(); instance.threshold = json.threshold || 1; // Parse oracle announcements using proper fromJSON method const announcements = json.oracleAnnouncements || json.oracle_announcements || []; // eslint-disable-next-line @typescript-eslint/no-explicit-any instance.announcements = announcements.map((announcementJson: any) => OracleAnnouncement.fromJSON(announcementJson), ); // Parse oracle params if present (null means explicitly absent) const oracleParamsData = json.oracleParams || json.oracle_params; if (oracleParamsData !== null && oracleParamsData !== undefined) { // Create OracleParams from JSON data using the fromJSON method instance.oracleParams = OracleParams.fromJSON(oracleParamsData); } else { // Explicitly null/undefined - will serialize as 00 (not present) instance.oracleParams = undefined; } return instance; } /** * Deserializes multi oracle info */ public static deserialize(buf: Buffer): MultiOracleInfo { const instance = new MultiOracleInfo(); const reader = new BufferReader(buf); // Read the type and length that serialize() writes reader.readBigSize(); // read type instance.length = reader.readBigSize(); // read length // In rust-dlc format, MultiOracleInfo body is: threshold + announcements + optional oracle params instance.threshold = reader.readUInt16BE(); const numAnnouncements = Number(reader.readBigSize()); // Changed from readUInt16BE to readBigSize to match rust-dlc vec_cb for (let i = 0; i < numAnnouncements; i++) { instance.announcements.push( OracleAnnouncement.deserialize(getTlv(reader)), ); } // Optional oracle params using Optional sub-type format const oracleParamsData = reader.readOptional(); if (oracleParamsData) { instance.oracleParams = OracleParams.deserializeBody(oracleParamsData); } return instance; } public type = MultiOracleInfo.type; public length: bigint; /** The threshold to be used for the contract (e.g. 2 of 3). */ public threshold: number; /** The set of oracle announcements. */ public announcements: OracleAnnouncement[] = []; /** The parameters to be used when allowing differences between oracle outcomes in numerical outcome contracts. */ public oracleParams?: OracleParams; public validate(): void { if (this.threshold <= 0) { throw new Error('threshold must be greater than 0'); } if (this.threshold > this.announcements.length) { throw new Error( 'threshold cannot be greater than number of announcements', ); } if (this.announcements.length === 0) { throw new Error('must have at least one announcement'); } // Validate all announcements this.announcements.forEach((announcement) => announcement.validate()); // Validate oracle params if present if (this.oracleParams) { this.oracleParams.validate(); } } /** * Returns the closest maturity date amongst all events */ public getClosestMaturityDate(): number { return Math.min( ...this.announcements.map((a) => a.oracleEvent.eventMaturityEpoch), ); } public toJSON(): MultiOracleInfoJSON { // Return enum variant format for Rust compatibility return { multi: { threshold: this.threshold, oracleAnnouncements: this.announcements.map((a) => a.toJSON()), oracleParams: this.oracleParams?.toJSON(), }, }; } public serialize(): Buffer { const writer = new BufferWriter(); // writer.writeBigSize(this.type); const dataWriter = new BufferWriter(); dataWriter.writeUInt16BE(this.threshold); dataWriter.writeBigSize(this.announcements.length); // Changed from writeUInt16BE to writeBigSize to match rust-dlc vec_cb for (const announcement of this.announcements) { dataWriter.writeBytes(announcement.serialize()); } // Use Optional serialization for oracle params (body content only, not TLV wrapped) const oracleParamsData = this.oracleParams ? this.oracleParams.serializeBody() : null; dataWriter.writeOptional(oracleParamsData); // writer.writeBigSize(dataWriter.size); writer.writeBytes(dataWriter.toBuffer()); return writer.toBuffer(); } /** * Serializes the body without TLV wrapper (for embedding in ContractInfo) * This matches rust-dlc behavior where OracleInfo.write() doesn't add type_id */ public serializeBody(): Buffer { const writer = new BufferWriter(); writer.writeUInt16BE(this.threshold); writer.writeBigSize(this.announcements.length); // Changed from writeUInt16BE to writeBigSize to match rust-dlc vec_cb for (const announcement of this.announcements) { writer.writeBytes(announcement.serialize()); } // Use Optional serialization for oracle params (body content only, not TLV wrapped) const oracleParamsData = this.oracleParams ? this.oracleParams.serializeBody() : null; writer.writeOptional(oracleParamsData); return writer.toBuffer(); } /** * Deserializes the body without TLV wrapper (for embedding in ContractInfo) * This matches rust-dlc behavior where OracleInfo is read without type_id */ public static deserializeBody(buf: Buffer): MultiOracleInfo { const instance = new MultiOracleInfo(); const reader = new BufferReader(buf); // No type/length to read - directly read the multi-oracle body instance.threshold = reader.readUInt16BE(); const numAnnouncements = Number(reader.readBigSize()); // BigSize for announcements count for (let i = 0; i < numAnnouncements; i++) { instance.announcements.push( OracleAnnouncement.deserialize(getTlv(reader)), ); } // Optional oracle params using Optional sub-type format const oracleParamsData = reader.readOptional(); if (oracleParamsData) { instance.oracleParams = OracleParams.deserializeBody(oracleParamsData); } return instance; } } /** * OracleInfo contains information about the oracles to be used in * executing a DLC. Updated to support both single and multi-oracle * patterns as per rust-dlc specification. */ export abstract class OracleInfo implements IDlcMessage { public static deserialize(buf: Buffer): SingleOracleInfo | MultiOracleInfo { const reader = new BufferReader(buf); const type = Number(reader.readBigSize()); switch (type) { case MessageType.SingleOracleInfo: return SingleOracleInfo.deserialize(buf); case MessageType.MultiOracleInfo: return MultiOracleInfo.deserialize(buf); default: throw new Error(`Unknown oracle info type: ${type}`); } } /** * Creates an OracleInfo from JSON data (e.g., from test vectors) * @param json JSON object representing oracle info */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any public static fromJSON(json: any): OracleInfo { if (!json) { throw new Error('oracleInfo is required'); } // Handle direct single oracle (legacy format) if (json.announcement) { return SingleOracleInfo.fromJSON(json); } // Handle wrapped single oracle else if (json.single) { return SingleOracleInfo.fromJSON(json.single); } // Handle multi oracle else if (json.multi) { return MultiOracleInfo.fromJSON(json.multi); } else { throw new Error( 'oracleInfo must have either announcement, single, or multi', ); } } public abstract type: number; public abstract validate(): void; public abstract toJSON(): SingleOracleInfoJSON | MultiOracleInfoJSON; public abstract serialize(): Buffer; public abstract serializeBody(): Buffer; /** * Returns the closest maturity date amongst all events */ public getClosestMaturityDate(): number { if (this instanceof SingleOracleInfo) { return this.announcement.oracleEvent.eventMaturityEpoch; } else if (this instanceof MultiOracleInfo) { return this.getClosestMaturityDate(); } throw new Error('Unknown oracle info type'); } } // For backward compatibility, keep the V0 class as alias to SingleOracleInfo export class OracleInfoV0 extends SingleOracleInfo { /** * Creates an OracleInfoV0 from JSON data (alias for SingleOracleInfo.fromJSON) * @param json JSON object representing oracle info */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any public static fromJSON(json: any): OracleInfoV0 { return SingleOracleInfo.fromJSON(json) as OracleInfoV0; } } // JSON interfaces export interface OracleParamsJSON { type?: number; // Made optional for rust-dlc compatibility maxErrorExp: number; minFailExp: number; maximizeCoverage: boolean; } // Rust-dlc enum variant format for SingleOracleInfo export interface SingleOracleInfoJSON { single: { oracleAnnouncement: OracleAnnouncementJSON; }; } // Rust-dlc enum variant format for MultiOracleInfo export interface MultiOracleInfoJSON { multi: { threshold: number; oracleAnnouncements: OracleAnnouncementJSON[]; oracleParams?: OracleParamsJSON; }; } // Backward compatibility type alias export type OracleInfoV0JSON = SingleOracleInfoJSON;