@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
342 lines (264 loc) • 10.3 kB
text/typescript
import { AddressMap } from '../deterministic/AddressMap.js';
import { Address } from '../keypair/Address.js';
import { BufferHelper } from '../utils/BufferHelper.js';
import {
ADDRESS_BYTE_LENGTH,
I128_BYTE_LENGTH,
U128_BYTE_LENGTH,
U16_BYTE_LENGTH,
U256_BYTE_LENGTH,
U32_BYTE_LENGTH,
U64_BYTE_LENGTH,
U8_BYTE_LENGTH,
} from '../utils/lengths.js';
import { i32, Selector, u16, u32, u64, u8 } from '../utils/types.js';
import { BinaryReader } from './BinaryReader.js';
export class BinaryWriter {
private currentOffset: u32 = 0;
private buffer: DataView;
constructor(length: number = 0) {
this.buffer = this.getDefaultBuffer(length);
}
public writeU8(value: u8): void {
if (value > 255) throw new Error('u8 value is too large.');
this.allocSafe(U8_BYTE_LENGTH);
this.buffer.setUint8(this.currentOffset++, value);
}
public writeU16(value: u16, be: boolean = true): void {
if (value > 65535) throw new Error('u16 value is too large.');
this.allocSafe(U16_BYTE_LENGTH);
this.buffer.setUint16(this.currentOffset, value, !be);
this.currentOffset += 2;
}
public writeU32(value: u32, be: boolean = true): void {
if (value > 4294967295) throw new Error('u32 value is too large.');
this.allocSafe(U32_BYTE_LENGTH);
this.buffer.setUint32(this.currentOffset, value, !be);
this.currentOffset += 4;
}
public writeU64(value: u64, be: boolean = true): void {
if (value > 18446744073709551615n) throw new Error('u64 value is too large.');
this.allocSafe(U64_BYTE_LENGTH);
this.buffer.setBigUint64(this.currentOffset, value, !be);
this.currentOffset += 8;
}
public writeSelector(value: Selector): void {
this.writeU32(value, true);
}
public writeBoolean(value: boolean): void {
this.writeU8(value ? 1 : 0);
}
public writeI128(bigIntValue: bigint, be: boolean = true): void {
if (
bigIntValue > 170141183460469231731687303715884105727n ||
bigIntValue < -170141183460469231731687303715884105728n
) {
throw new Error('i128 value is too large.');
}
this.allocSafe(I128_BYTE_LENGTH);
const bytesToHex = BufferHelper.valueToUint8Array(bigIntValue, I128_BYTE_LENGTH);
if (bytesToHex.byteLength !== I128_BYTE_LENGTH) {
throw new Error(`Invalid i128 value: ${bigIntValue}`);
}
if (be) {
for (let i = 0; i < bytesToHex.byteLength; i++) {
this.writeU8(bytesToHex[i]);
}
} else {
for (let i = bytesToHex.byteLength - 1; i >= 0; i--) {
this.writeU8(bytesToHex[i]);
}
}
}
public writeU256(bigIntValue: bigint, be: boolean = true): void {
if (
bigIntValue >
115792089237316195423570985008687907853269984665640564039457584007913129639935n &&
bigIntValue < 0n
) {
throw new Error('u256 value is too large or negative.');
}
this.allocSafe(U256_BYTE_LENGTH);
const bytesToHex = BufferHelper.valueToUint8Array(bigIntValue);
if (bytesToHex.byteLength !== U256_BYTE_LENGTH) {
throw new Error(`Invalid u256 value: ${bigIntValue}`);
}
if (be) {
for (let i = 0; i < bytesToHex.byteLength; i++) {
this.writeU8(bytesToHex[i]);
}
} else {
for (let i = bytesToHex.byteLength - 1; i >= 0; i--) {
this.writeU8(bytesToHex[i]);
}
}
}
public writeU128(bigIntValue: bigint, be: boolean = true): void {
if (bigIntValue > 340282366920938463463374607431768211455n && bigIntValue < 0n) {
throw new Error('u128 value is too large or negative.');
}
this.allocSafe(U128_BYTE_LENGTH);
const bytesToHex = BufferHelper.valueToUint8Array(bigIntValue, U128_BYTE_LENGTH);
if (bytesToHex.byteLength !== U128_BYTE_LENGTH) {
throw new Error(`Invalid u128 value: ${bigIntValue}`);
}
if (be) {
for (let i = 0; i < bytesToHex.byteLength; i++) {
this.writeU8(bytesToHex[i]);
}
} else {
for (let i = bytesToHex.byteLength - 1; i >= 0; i--) {
this.writeU8(bytesToHex[i]);
}
}
}
public writeBytes(value: Uint8Array | Buffer): void {
this.allocSafe(value.byteLength);
for (let i = 0; i < value.byteLength; i++) {
this.writeU8(value[i]);
}
}
public writeString(value: string): void {
const encoder = new TextEncoder();
const bytes = encoder.encode(value);
this.allocSafe(bytes.length);
this.writeBytes(bytes);
}
public writeStringWithLength(value: string): void {
const encoder = new TextEncoder();
const bytes = encoder.encode(value);
this.allocSafe(U32_BYTE_LENGTH + bytes.length);
this.writeU32(bytes.length);
this.writeBytes(bytes);
}
public writeAddress(value: Address): void {
this.verifyAddress(value);
this.writeBytes(value);
}
public getBuffer(clear: boolean = true): Uint8Array {
const buf = new Uint8Array(this.buffer.byteLength);
for (let i: u32 = 0; i < this.buffer.byteLength; i++) {
buf[i] = this.buffer.getUint8(i);
}
if (clear) this.clear();
return buf;
}
public reset(): void {
this.currentOffset = 0;
this.buffer = this.getDefaultBuffer(4);
}
public toBytesReader(): BinaryReader {
return new BinaryReader(this.getBuffer());
}
public getOffset(): u32 {
return this.currentOffset;
}
public setOffset(offset: u32): void {
this.currentOffset = offset;
}
public clear(): void {
this.currentOffset = 0;
this.buffer = this.getDefaultBuffer();
}
public allocSafe(size: u32): void {
if (this.currentOffset + size > this.buffer.byteLength) {
this.resize(size);
}
}
public writeAddressValueTuple(map: AddressMap<bigint>, be: boolean = true): void {
if (map.size > 65535) throw new Error('Map size is too large');
this.writeU16(map.size, be);
const keys = Array.from(map.keys());
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = map.get(key);
if (value === null || value === undefined) throw new Error('Value not found');
this.writeAddress(key);
this.writeU256(value, be);
}
}
public writeBytesWithLength(value: Uint8Array): void {
this.writeU32(value.length);
this.writeBytes(value);
}
public writeAddressArray(value: Address[]): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length);
for (let i = 0; i < value.length; i++) {
this.writeAddress(value[i]);
}
}
public writeU32Array(value: u32[], be: boolean = true): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length, be);
for (let i = 0; i < value.length; i++) {
this.writeU32(value[i], be);
}
}
public writeU256Array(value: bigint[], be: boolean = true): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length, be);
for (let i = 0; i < value.length; i++) {
this.writeU256(value[i], be);
}
}
public writeU128Array(value: bigint[], be: boolean = true): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length, be);
for (let i = 0; i < value.length; i++) {
this.writeU128(value[i], be);
}
}
public writeStringArray(value: string[]): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length);
for (let i = 0; i < value.length; i++) {
this.writeStringWithLength(value[i]);
}
}
public writeU16Array(value: u16[], be: boolean = true): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length, be);
for (let i = 0; i < value.length; i++) {
this.writeU16(value[i], be);
}
}
public writeU8Array(value: u8[]): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length);
for (let i = 0; i < value.length; i++) {
this.writeU8(value[i]);
}
}
public writeU64Array(value: bigint[], be: boolean = true): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length, be);
for (let i = 0; i < value.length; i++) {
this.writeU64(value[i], be);
}
}
public writeBytesArray(value: Uint8Array[]): void {
if (value.length > 65535) throw new Error('Array size is too large');
this.writeU16(value.length);
for (let i = 0; i < value.length; i++) {
this.writeBytesWithLength(value[i]);
}
}
private verifyAddress(pubKey: Address): void {
if (pubKey.byteLength > ADDRESS_BYTE_LENGTH) {
throw new Error(
`Address is too long ${pubKey.byteLength} > ${ADDRESS_BYTE_LENGTH} bytes`,
);
}
}
private resize(size: u32): void {
const buf: Uint8Array = new Uint8Array(this.buffer.byteLength + size);
for (let i: i32 = 0; i < this.buffer.byteLength; i++) {
buf[i] = this.buffer.getUint8(i);
}
this.buffer = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
}
private getDefaultBuffer(length: number = 0): DataView {
return new DataView(new ArrayBuffer(length));
}
}