import { cborBackend } from "cbor-rpc"; import { addExtension, Tag } from "cbor-rpc/lib/cbor-x"; import { HexString } from "./value"; import { Buffer } from "buffer"; export type PlutusV1Script = Buffer; export type PlutusV2Script = Buffer; export type PlutusV3Script = Buffer; export type DatumOption = HexString | PlutusData; type PlutusDataKind = "constr" | "map" | "list" | "int" | "bytes"; export class PlutusData { kind: PlutusDataKind; value: any; tag?: number; private constructor(kind: PlutusDataKind, value: any, tag?: number) { this.kind = kind; this.value = value; this.tag = tag; } static fromCborObject(obj: any): PlutusData { if (Buffer.isBuffer(obj)) { return new PlutusData("bytes", obj); } if (typeof obj === "number" || typeof obj === "bigint") { return new PlutusData("int", BigInt(obj)); } if (Array.isArray(obj)) { return new PlutusData("list", obj.map(PlutusData.fromCborObject)); } if (obj instanceof Map) { const map: [PlutusData, PlutusData][] = []; for (const [k, v] of obj.entries()) { map.push([PlutusData.fromCborObject(k), PlutusData.fromCborObject(v)]); } return new PlutusData("map", map); } if (obj?.tag !== undefined && obj?.value !== undefined) { const tag = obj.tag; const val = obj.value; // Big uint: #6.2 if (tag === 2) { return new PlutusData("int", BigInt("0x" + Buffer.from(val).toString("hex"))); } // Big nint: #6.3 if (tag === 3) { const n = BigInt("0x" + Buffer.from(val).toString("hex")); return new PlutusData("int", -BigInt(1) - n); } // Constr: #6.121 to #6.127, #6.1280–#6.1400 if ((tag >= 121 && tag <= 127) || (tag >= 1280 && tag <= 1400)) { return new PlutusData("constr", val.map(PlutusData.fromCborObject), tag); } // Constr: tag 102 = [idx, [args]] if (tag === 102 && Array.isArray(val) && val.length === 2) { const [idx, args] = val; return new PlutusData("constr", args.map(PlutusData.fromCborObject), idx); } // Fall back to bytes if unknown return new PlutusData("bytes", Buffer.from(val)); } throw new Error(`Unsupported CBOR term: ${JSON.stringify(obj)}`); } static fromJSON(datumJSON: any): PlutusData { const cbor = fromJsonToCborObject(datumJSON); return PlutusData.fromCborObject(cbor); } static fromHex(cborHex: string): PlutusData { const cborObject = cborBackend.decode(Buffer.from(cborHex, "hex")); return PlutusData.fromCborObject(cborObject); } static fromBytes(datumBytes: Buffer): PlutusData { const cborObject = cborBackend.decode(datumBytes); return PlutusData.fromCborObject(cborObject); } toCborObject(): any { const json = this.toJSON(); return fromJsonToCborObject(json); } toJSON(): any { switch (this.kind) { case "bytes": return { bytes: this.value.toString("hex") }; case "int": return { int: parseInt(this.value) }; case "list": return { list: this.value.map((v: PlutusData) => v.toJSON()) }; case "map": return { map: this.value.map(([k, v]: [PlutusData, PlutusData]) => ({ k: k.toJSON(), v: v.toJSON(), })), }; case "constr": return { fields: this.value.map((v: PlutusData) => v.toJSON()), constructor: this.tag ? this.tag % 121 : 0, }; } } toHex(): string { const cborObject = this.toCborObject(); return cborBackend.encode(cborObject).toString("hex"); } toBytes(): Buffer { const cborObject = this.toCborObject(); return cborBackend.encode(cborObject); } toInlineDatumBytes(): Buffer { const datumBytes = this.toBytes(); const inlineDatumTag = new Tag(datumBytes, 24); const finalArray = [1, inlineDatumTag]; return cborBackend.encode(finalArray); } toInlineDatumHex(): string { return this.toInlineDatumBytes().toString("hex"); } } function fromJsonToCborObject(input: any): any { if ("bytes" in input) { return Buffer.from(input.bytes, "hex"); } if ("int" in input) { const int = BigInt(input.int); if (int >= BigInt(0) && int <= BigInt(Number.MAX_SAFE_INTEGER)) { return Number(int); } else if (int >= BigInt(0)) { const hex = int.toString(16); const buf = Buffer.from(hex.length % 2 === 1 ? "0" + hex : hex, "hex"); return new Tag(buf, 2); } else { const abs = -BigInt(1) - int; const hex = abs.toString(16); const buf = Buffer.from(hex.length % 2 === 1 ? "0" + hex : hex, "hex"); return new Tag(buf, 3); } } if ("list" in input) { const items = input.list.map((v: any) => fromJsonToCborObject(v)); items._cbor_indef = true; return items; } if ("map" in input) { const m = new Map(); for (const { k, v } of input.map) { m.set(fromJsonToCborObject(k), fromJsonToCborObject(v)); } m.set("_cbor_indef", true); return m; } if ("fields" in input && "constructor" in input) { const tag = 121 + input.constructor; const args = input.fields.map((v: any) => fromJsonToCborObject(v)); if (input.fields.length != 0) args._cbor_indef = true; return new Tag(args, tag); } return input; }