// @flow function dataLengthError(actual, required) { throw new Error(`Invalid data length! Required: ${required}, actual: ${actual}`); } function assertDataLength(actual, required) { if (actual !== required) { dataLengthError(actual, required); } } function assertArrayBuffer(reader, padTo) { if (typeof reader === "string") { reader = reader.replace(/^0x/,"") if(padTo != undefined) { // padTo is defined for numbers, which are stored bigendian and non-multiple-of-2. reader = reader.padStart(padTo*2, "0") reader = Buffer.from(reader.match(/../g).reverse().join(""), 'hex'); } else { reader = Buffer.from(reader, 'hex'); } reader = reader.buffer.slice(reader.byteOffset,reader.byteOffset+reader.byteLength); } if (BigInt && reader instanceof BigInt) { var tmp = new ArrayBuffer(padTo); var tmpDV = new DataView(tmp); tmpDV.setBigUint64(0, value, true); return tmp; } if (reader instanceof Object && reader.toArrayBuffer instanceof Function) { reader = reader.toArrayBuffer(); } if (!(reader instanceof ArrayBuffer)) { throw new Error("Provided value must be an ArrayBuffer or can be transformed into ArrayBuffer!"); } return reader; } function verifyAndExtractOffsets(view, expectedFieldCount, compatible) { if (view.byteLength < 4) { dataLengthError(view.byteLength, ">4"); } const requiredByteLength = view.getUint32(0, true); assertDataLength(view.byteLength, requiredByteLength); if (requiredByteLength === 4) { return [requiredByteLength]; } if (requiredByteLength < 8) { dataLengthError(view.byteLength, ">8"); } const firstOffset = view.getUint32(4, true); if (firstOffset % 4 !== 0 || firstOffset < 8) { throw new Error(`Invalid first offset: ${firstOffset}`); } const itemCount = firstOffset / 4 - 1; if (itemCount < expectedFieldCount) { throw new Error(`Item count not enough! Required: ${expectedFieldCount}, actual: ${itemCount}`); } else if ((!compatible) && itemCount > expectedFieldCount) { throw new Error(`Item count is more than required! Required: ${expectedFieldCount}, actual: ${itemCount}`); } if (requiredByteLength < firstOffset) { throw new Error(`First offset is larger than byte length: ${firstOffset}`); } const offsets = []; for (let i = 0; i < itemCount; i++) { const start = 4 + i * 4; offsets.push(view.getUint32(start, true)); } offsets.push(requiredByteLength); for (let i = 0; i < offsets.length - 1; i++) { if (offsets[i] > offsets[i + 1]) { throw new Error(`Offset index ${i}: ${offsets[i]} is larger than offset index ${i + 1}: ${offsets[i + 1]}`); } } return offsets; } function fromStringEnum(val) { switch(typeof val) { case "string": switch(val.toLowerCase()) { case "code": return 0; case "dep_group": return 1; // The last 1 bit represents using type hash. // If the last bit is 0, it indicates using code hash to locate the contract code. // The first 7 bits prepresent the VM version to run the contract code. case "type": return 0b00000001; case "data": return 0b00000000; case "data1": return 0b00000010; case "data2": return 0b00000100; default: throw new Error("Not a valid byte representation: "+val); } case "number": return val; default: throw new Error("Not a valid byte representation: "+val); } } function serializeTable(buffers) { const itemCount = buffers.length; let totalSize = 4 * (itemCount + 1); const offsets = []; for (let i = 0; i < itemCount; i++) { offsets.push(totalSize); totalSize += buffers[i].byteLength; } const buffer = new ArrayBuffer(totalSize); const array = new Uint8Array(buffer); const view = new DataView(buffer); view.setUint32(0, totalSize, true); for (let i = 0; i < itemCount; i++) { view.setUint32(4 + i * 4, offsets[i], true); array.set(new Uint8Array(buffers[i]), offsets[i]); } return buffer; } export class AnnotatedCellInput { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(_compatible) { const offsets = verifyAndExtractOffsets(this.view, 0, true); new CellInput(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); new RawTransaction(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); } getInput() { const start = 4; const offset = this.view.getUint32(start, true); const offset_end = this.view.getUint32(start + 4, true); return new CellInput(this.view.buffer.slice(offset, offset_end), { validate: false }); } getSource() { const start = 8; const offset = this.view.getUint32(start, true); const offset_end = this.view.byteLength; return new RawTransaction(this.view.buffer.slice(offset, offset_end), { validate: false }); } toObject() { let obj={}; obj["input"]=this.getInput().toObject(); obj["source"]=this.getSource().toObject(); return obj; } } export function SerializeAnnotatedCellInput(value) { if(typeof value === "object" && value !== null && "view" in value) return value.view.buffer; const buffers = []; buffers.push(SerializeCellInput(value.input)); buffers.push(SerializeRawTransaction(value.source)); return serializeTable(buffers); } export class AnnotatedCellInputVec { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(_compatible) { const offsets = verifyAndExtractOffsets(this.view, 0, true); for (let i = 0; i < offsets.length - 1; i++) { new AnnotatedCellInput(this.view.buffer.slice(offsets[i], offsets[i + 1]), { validate: false }).validate(); } } length() { if (this.view.byteLength < 8) { return 0; } else { return this.view.getUint32(4, true) / 4 - 1; } } indexAt(i) { const start = 4 + i * 4; const offset = this.view.getUint32(start, true); let offset_end = this.view.byteLength; if (i + 1 < this.length()) { offset_end = this.view.getUint32(start + 4, true); } return new AnnotatedCellInput(this.view.buffer.slice(offset, offset_end), { validate: false }); } toObject() { const len=this.length(); var rv=[]; for(var i=0;i SerializeAnnotatedCellInput(item))); } export class AnnotatedRawTransaction { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(_compatible) { const offsets = verifyAndExtractOffsets(this.view, 0, true); new Uint32(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); new CellDepVec(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); new Byte32Vec(this.view.buffer.slice(offsets[2], offsets[3]), { validate: false }).validate(); new AnnotatedCellInputVec(this.view.buffer.slice(offsets[3], offsets[4]), { validate: false }).validate(); new CellOutputVec(this.view.buffer.slice(offsets[4], offsets[5]), { validate: false }).validate(); new BytesVec(this.view.buffer.slice(offsets[5], offsets[6]), { validate: false }).validate(); } getVersion() { const start = 4; const offset = this.view.getUint32(start, true); const offset_end = this.view.getUint32(start + 4, true); return new Uint32(this.view.buffer.slice(offset, offset_end), { validate: false }); } getCellDeps() { const start = 8; const offset = this.view.getUint32(start, true); const offset_end = this.view.getUint32(start + 4, true); return new CellDepVec(this.view.buffer.slice(offset, offset_end), { validate: false }); } getHeaderDeps() { const start = 12; const offset = this.view.getUint32(start, true); const offset_end = this.view.getUint32(start + 4, true); return new Byte32Vec(this.view.buffer.slice(offset, offset_end), { validate: false }); } getInputs() { const start = 16; const offset = this.view.getUint32(start, true); const offset_end = this.view.getUint32(start + 4, true); return new AnnotatedCellInputVec(this.view.buffer.slice(offset, offset_end), { validate: false }); } getOutputs() { const start = 20; const offset = this.view.getUint32(start, true); const offset_end = this.view.getUint32(start + 4, true); return new CellOutputVec(this.view.buffer.slice(offset, offset_end), { validate: false }); } getOutputsData() { const start = 24; const offset = this.view.getUint32(start, true); const offset_end = this.view.byteLength; return new BytesVec(this.view.buffer.slice(offset, offset_end), { validate: false }); } toObject() { let obj={}; obj["version"]=this.getVersion().toObject(); obj["cell_deps"]=this.getCellDeps().toObject(); obj["header_deps"]=this.getHeaderDeps().toObject(); obj["inputs"]=this.getInputs().toObject(); obj["outputs"]=this.getOutputs().toObject(); obj["outputs_data"]=this.getOutputsData().toObject(); return obj; } } export function SerializeAnnotatedRawTransaction(value) { if(typeof value === "object" && value !== null && "view" in value) return value.view.buffer; const buffers = []; buffers.push(SerializeUint32(value.version)); buffers.push(SerializeCellDepVec(value.cell_deps)); buffers.push(SerializeByte32Vec(value.header_deps)); buffers.push(SerializeAnnotatedCellInputVec(value.inputs)); buffers.push(SerializeCellOutputVec(value.outputs)); buffers.push(SerializeBytesVec(value.outputs_data)); return serializeTable(buffers); } export class Bip32 { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(compatible = false) { if (this.view.byteLength < 4) { dataLengthError(this.view.byteLength, ">4"); } const requiredByteLength = this.length() * Uint32.size() + 4; assertDataLength(this.view.byteLength, requiredByteLength); for (let i = 0; i < 0; i++) { const item = this.indexAt(i); item.validate(compatible); } } indexAt(i) { return new Uint32(this.view.buffer.slice(4 + i * Uint32.size(), 4 + (i + 1) * Uint32.size()), { validate: false }); } toObject() { const len=this.length(); var rv=[]; for(var i=0;i4") } const requiredByteLength = this.length() + 4; assertDataLength(this.view.byteLength, requiredByteLength); } raw() { return this.view.buffer.slice(4); } indexAt(i) { return this.view.getUint8(4 + i); } toObject() { return Buffer.from(this.raw()).toString('hex'); } length() { return this.view.getUint32(0, true); } } export function SerializeBytes(value) { if(typeof value === "object" && value !== null && "view" in value) return value.view.buffer; const item = assertArrayBuffer(value); const array = new Uint8Array(4 + item.byteLength); (new DataView(array.buffer)).setUint32(0, item.byteLength, true); array.set(new Uint8Array(item), 4); return array.buffer; } export class BytesOpt { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(compatible = false) { if (this.hasValue()) { this.value().validate(compatible); } } value() { return new Bytes(this.view.buffer, { validate: false }); } toObject() { if(this.hasValue()) return this.value().toObject(); return null; } hasValue() { return this.view.byteLength > 0; } } export function SerializeBytesOpt(value) { if(typeof value === "object" && value !== null && "view" in value) return value.view.buffer; if (value) { return SerializeBytes(value); } else { return new ArrayBuffer(0); } } export class BytesVec { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(_compatible) { const offsets = verifyAndExtractOffsets(this.view, 0, true); for (let i = 0; i < offsets.length - 1; i++) { new Bytes(this.view.buffer.slice(offsets[i], offsets[i + 1]), { validate: false }).validate(); } } length() { if (this.view.byteLength < 8) { return 0; } else { return this.view.getUint32(4, true) / 4 - 1; } } indexAt(i) { const start = 4 + i * 4; const offset = this.view.getUint32(start, true); let offset_end = this.view.byteLength; if (i + 1 < this.length()) { offset_end = this.view.getUint32(start + 4, true); } return new Bytes(this.view.buffer.slice(offset, offset_end), { validate: false }); } toObject() { const len=this.length(); var rv=[]; for(var i=0;i SerializeBytes(item))); } export class Byte32Vec { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(compatible = false) { if (this.view.byteLength < 4) { dataLengthError(this.view.byteLength, ">4"); } const requiredByteLength = this.length() * Byte32.size() + 4; assertDataLength(this.view.byteLength, requiredByteLength); for (let i = 0; i < 0; i++) { const item = this.indexAt(i); item.validate(compatible); } } indexAt(i) { return new Byte32(this.view.buffer.slice(4 + i * Byte32.size(), 4 + (i + 1) * Byte32.size()), { validate: false }); } toObject() { const len=this.length(); var rv=[]; for(var i=0;i 0; } } export function SerializeScriptOpt(value) { if(typeof value === "object" && value !== null && "view" in value) return value.view.buffer; if (value) { return SerializeScript(value); } else { return new ArrayBuffer(0); } } export class OutPoint { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } getTxHash() { return new Byte32(this.view.buffer.slice(0, 0 + Byte32.size()), { validate: false }); } getIndex() { return new Uint32(this.view.buffer.slice(0 + Byte32.size(), 0 + Byte32.size() + Uint32.size()), { validate: false }); } validate(compatible = false) { assertDataLength(this.view.byteLength, OutPoint.size()); this.getTxHash().validate(compatible); this.getIndex().validate(compatible); } static size() { return 0 + Byte32.size() + Uint32.size(); } toObject() { let obj={}; obj["tx_hash"]=this.getTxHash().toObject(); obj["index"]=this.getIndex().toObject(); return obj; } } export function SerializeOutPoint(value) { if(typeof value === "object" && value !== null && "view" in value) return value.view.buffer; const array = new Uint8Array(0 + Byte32.size() + Uint32.size()); array.set(new Uint8Array(SerializeByte32(value.tx_hash)), 0); array.set(new Uint8Array(SerializeUint32(value.index)), 0 + Byte32.size()); return array.buffer; } export class CellInput { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } getSince() { return new Uint64(this.view.buffer.slice(0, 0 + Uint64.size()), { validate: false }); } getPreviousOutput() { return new OutPoint(this.view.buffer.slice(0 + Uint64.size(), 0 + Uint64.size() + OutPoint.size()), { validate: false }); } validate(compatible = false) { assertDataLength(this.view.byteLength, CellInput.size()); this.getSince().validate(compatible); this.getPreviousOutput().validate(compatible); } static size() { return 0 + Uint64.size() + OutPoint.size(); } toObject() { let obj={}; obj["since"]=this.getSince().toObject(); obj["previous_output"]=this.getPreviousOutput().toObject(); return obj; } } export function SerializeCellInput(value) { if(typeof value === "object" && value !== null && "view" in value) return value.view.buffer; const array = new Uint8Array(0 + Uint64.size() + OutPoint.size()); array.set(new Uint8Array(SerializeUint64(value.since)), 0); array.set(new Uint8Array(SerializeOutPoint(value.previous_output)), 0 + Uint64.size()); return array.buffer; } export class CellInputVec { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(compatible = false) { if (this.view.byteLength < 4) { dataLengthError(this.view.byteLength, ">4"); } const requiredByteLength = this.length() * CellInput.size() + 4; assertDataLength(this.view.byteLength, requiredByteLength); for (let i = 0; i < 0; i++) { const item = this.indexAt(i); item.validate(compatible); } } indexAt(i) { return new CellInput(this.view.buffer.slice(4 + i * CellInput.size(), 4 + (i + 1) * CellInput.size()), { validate: false }); } toObject() { const len=this.length(); var rv=[]; for(var i=0;i SerializeCellOutput(item))); } export class CellDep { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } getOutPoint() { return new OutPoint(this.view.buffer.slice(0, 0 + OutPoint.size()), { validate: false }); } getDepType() { return this.view.getUint8(0 + OutPoint.size()); } validate(compatible = false) { assertDataLength(this.view.byteLength, CellDep.size()); this.getOutPoint().validate(compatible); } static size() { return 0 + OutPoint.size() + 1; } toObject() { let obj={}; obj["out_point"]=this.getOutPoint().toObject(); obj["dep_type"]=this.getDepType(); return obj; } } export function SerializeCellDep(value) { if(typeof value === "object" && value !== null && "view" in value) return value.view.buffer; const array = new Uint8Array(0 + OutPoint.size() + 1); array.set(new Uint8Array(SerializeOutPoint(value.out_point)), 0); const view = new DataView(array.buffer); view.setUint8(0 + OutPoint.size(), fromStringEnum(value.dep_type)); return array.buffer; } export class CellDepVec { constructor(reader, { validate = true } = {}) { this.view = new DataView(assertArrayBuffer(reader)); if (validate) { this.validate(); } } validate(compatible = false) { if (this.view.byteLength < 4) { dataLengthError(this.view.byteLength, ">4"); } const requiredByteLength = this.length() * CellDep.size() + 4; assertDataLength(this.view.byteLength, requiredByteLength); for (let i = 0; i < 0; i++) { const item = this.indexAt(i); item.validate(compatible); } } indexAt(i) { return new CellDep(this.view.buffer.slice(4 + i * CellDep.size(), 4 + (i + 1) * CellDep.size()), { validate: false }); } toObject() { const len=this.length(); var rv=[]; for(var i=0;i