/** * Borsh Binary Encoder * * Implements the Borsh serialization format to match Rust's borsh crate * https://borsh.io/ */ export class BorshWriter { private buffer: number[] = []; /** * Write a single byte (u8) */ writeU8(value: number): void { this.buffer.push(value & 0xff); } /** * Write a 16-bit unsigned integer (u16) in little-endian */ writeU16(value: number): void { this.buffer.push(value & 0xff); this.buffer.push((value >> 8) & 0xff); } /** * Write a 32-bit unsigned integer (u32) in little-endian */ writeU32(value: number): void { this.buffer.push(value & 0xff); this.buffer.push((value >> 8) & 0xff); this.buffer.push((value >> 16) & 0xff); this.buffer.push((value >> 24) & 0xff); } /** * Write a 64-bit unsigned integer (u64) in little-endian */ writeU64(value: bigint): void { const num = BigInt(value); for (let i = 0; i < 8; i++) { this.buffer.push(Number((num >> BigInt(i * 8)) & BigInt(0xff))); } } /** * Write a 32-bit floating point number (f32) in little-endian */ writeF32(value: number): void { const view = new DataView(new ArrayBuffer(4)); view.setFloat32(0, value, true); for (let i = 0; i < 4; i++) { this.buffer.push(view.getUint8(i)); } } /** * Write a 64-bit floating point number (f64) in little-endian */ writeF64(value: number): void { const view = new DataView(new ArrayBuffer(8)); view.setFloat64(0, value, true); for (let i = 0; i < 8; i++) { this.buffer.push(view.getUint8(i)); } } /** * Write a fixed-size byte array */ writeFixedArray(bytes: Uint8Array): void { for (let i = 0; i < bytes.length; i++) { this.buffer.push(bytes[i]); } } /** * Write a variable-length byte array (u32 length + bytes) */ writeBytes(bytes: Uint8Array): void { this.writeU32(bytes.length); this.writeFixedArray(bytes); } /** * Write a vector (u32 length + items) */ writeVec(items: T[], writeFn: (item: T) => void): void { this.writeU32(items.length); for (const item of items) { writeFn(item); } } /** * Write a string (u32 length + UTF-8 bytes) * Uses TextEncoder to properly handle all Unicode characters including emojis */ writeString(str: string): void { // Use TextEncoder to properly encode UTF-8, handling all Unicode characters // including emojis and surrogate pairs correctly const encoder = new TextEncoder(); const bytes = encoder.encode(str); this.writeU32(bytes.length); for (let i = 0; i < bytes.length; i++) { this.buffer.push(bytes[i]); } } /** * Write an Option (1 byte + value if Some) */ writeOption(value: T | null | undefined, writeFn: (item: T) => void): void { if (value === null || value === undefined) { this.writeU8(0); // None } else { this.writeU8(1); // Some writeFn(value); } } /** * Get the serialized bytes */ toBytes(): Uint8Array { return new Uint8Array(this.buffer); } /** * Get current buffer size */ size(): number { return this.buffer.length; } }