UNPKG

opnet

Version:

The perfect library for building Bitcoin-based applications.

1,076 lines (740 loc) 19.6 kB
# Binary Serialization BinaryWriter and BinaryReader are low-level utilities for encoding and decoding binary data used in OPNet smart contract calldata. ## Table of Contents - [Overview](#overview) - [Installation](#installation) - [BinaryWriter](#binarywriter) - [Constructor](#constructor) - [Primitive Methods](#primitive-methods) - [String Methods](#string-methods) - [Bytes Methods](#bytes-methods) - [Address Methods](#address-methods) - [Array Methods](#array-methods) - [Map Methods](#map-methods) - [Output](#output) - [Utility Methods](#utility-methods) - [BinaryReader](#binaryreader) - [Constructor](#constructor-1) - [Primitive Methods](#primitive-methods-1) - [String Methods](#string-methods-1) - [Bytes Methods](#bytes-methods-1) - [Address Methods](#address-methods-1) - [Array Methods](#array-methods-1) - [Map Methods](#map-methods-1) - [Utility Methods](#utility-methods-1) - [Complete Examples](#complete-examples) - [Type Sizes](#type-sizes) - [Best Practices](#best-practices) --- ## Overview ```mermaid flowchart LR A[TypeScript Values] --> B[BinaryWriter] B --> C[Uint8Array] C --> D[Contract Call] D --> E[Response Bytes] E --> F[BinaryReader] F --> G[TypeScript Values] ``` These classes are provided by `@btc-vision/transaction` and are essential for: - Building custom calldata for contract interactions - Decoding raw contract responses - Creating deployment parameters - Working with low-level contract storage --- ## Installation ```bash npm install @btc-vision/transaction ``` ```typescript import { BinaryWriter, BinaryReader } from '@btc-vision/transaction'; ``` --- ## BinaryWriter Creates binary data by sequentially writing typed values. ### Constructor ```typescript const writer = new BinaryWriter(length?: number); ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `length` | `number` | `0` | Initial buffer size (auto-expands) | ### Primitive Methods #### writeU8 Write an unsigned 8-bit integer (0-255). ```typescript writeU8(value: number): void ``` ```typescript const writer = new BinaryWriter(); writer.writeU8(255); ``` #### writeU16 Write an unsigned 16-bit integer (0-65535). ```typescript writeU16(value: number, be?: boolean): void ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `value` | `number` | Required | Value to write | | `be` | `boolean` | `true` | Big-endian (true) or little-endian (false) | ```typescript writer.writeU16(1000); // Big-endian (default) writer.writeU16(1000, false); // Little-endian ``` #### writeU32 Write an unsigned 32-bit integer (0-4294967295). ```typescript writeU32(value: number, be?: boolean): void ``` ```typescript writer.writeU32(1000000); ``` #### writeU64 Write an unsigned 64-bit integer as bigint. ```typescript writeU64(value: bigint, be?: boolean): void ``` ```typescript writer.writeU64(1000000000000n); ``` #### writeU128 Write an unsigned 128-bit integer as bigint. ```typescript writeU128(value: bigint, be?: boolean): void ``` ```typescript writer.writeU128(340282366920938463463374607431768211455n); ``` #### writeU256 Write an unsigned 256-bit integer as bigint. Common for token amounts. ```typescript writeU256(value: bigint, be?: boolean): void ``` ```typescript // Write a token amount (100 tokens with 8 decimals) writer.writeU256(100_00000000n); ``` #### writeI128 Write a signed 128-bit integer as bigint. ```typescript writeI128(value: bigint, be?: boolean): void ``` ```typescript writer.writeI128(-50000n); ``` #### writeBoolean Write a boolean as a single byte (0 or 1). ```typescript writeBoolean(value: boolean): void ``` ```typescript writer.writeBoolean(true); // Writes 0x01 writer.writeBoolean(false); // Writes 0x00 ``` #### writeSelector Write a 4-byte function selector. ```typescript writeSelector(value: number): void ``` ```typescript // Function selector for "transfer(address,uint256)" writer.writeSelector(0xa9059cbb); ``` ### String Methods #### writeString Write a raw string without length prefix. ```typescript writeString(value: string): void ``` ```typescript writer.writeString("Hello"); ``` #### writeStringWithLength Write a string with a u32 length prefix. ```typescript writeStringWithLength(value: string): void ``` ```typescript writer.writeStringWithLength("Hello World"); // Writes: [4 bytes: length][UTF-8 bytes] ``` ### Bytes Methods #### writeBytes Write raw bytes. ```typescript writeBytes(value: Uint8Array): void ``` ```typescript const data = new Uint8Array([0x01, 0x02, 0x03]); writer.writeBytes(data); ``` #### writeBytesWithLength Write bytes with a u32 length prefix. ```typescript writeBytesWithLength(value: Uint8Array): void ``` ```typescript writer.writeBytesWithLength(new Uint8Array([0x01, 0x02, 0x03])); // Writes: [4 bytes: length][bytes] ``` ### Address Methods #### writeAddress Write an OPNet address (32 bytes). ```typescript writeAddress(value: Address): void ``` ```typescript import { Address } from '@btc-vision/transaction'; const recipient = Address.fromString('0x...'); writer.writeAddress(recipient); ``` ### Array Methods #### writeU8Array Write an array of u8 values with u16 length prefix. ```typescript writeU8Array(value: number[]): void ``` ```typescript writer.writeU8Array([1, 2, 3, 4, 5]); ``` #### writeU16Array Write an array of u16 values. ```typescript writeU16Array(value: number[], be?: boolean): void ``` #### writeU32Array Write an array of u32 values. ```typescript writeU32Array(value: number[], be?: boolean): void ``` #### writeU64Array Write an array of u64 (bigint) values. ```typescript writeU64Array(value: bigint[], be?: boolean): void ``` #### writeU128Array Write an array of u128 (bigint) values. ```typescript writeU128Array(value: bigint[], be?: boolean): void ``` #### writeU256Array Write an array of u256 (bigint) values. ```typescript writeU256Array(value: bigint[], be?: boolean): void ``` ```typescript // Write multiple token amounts writer.writeU256Array([100n, 200n, 300n]); ``` #### writeAddressArray Write an array of addresses. ```typescript writeAddressArray(value: Address[]): void ``` ```typescript const addresses: Address[] = [addr1, addr2, addr3]; writer.writeAddressArray(addresses); ``` #### writeStringArray Write an array of strings (each with length prefix). ```typescript writeStringArray(value: string[]): void ``` ```typescript writer.writeStringArray(["Alice", "Bob", "Charlie"]); ``` #### writeBytesArray Write an array of byte arrays. ```typescript writeBytesArray(value: Uint8Array[]): void ``` #### writeArrayOfBuffer Write an array of buffers with individual length prefixes. ```typescript writeArrayOfBuffer(values: Uint8Array[], be?: boolean): void ``` ### Map Methods #### writeAddressValueTuple Write a map of addresses to u256 values. ```typescript writeAddressValueTuple(map: AddressMap<bigint>, be?: boolean): void ``` ```typescript import { AddressMap } from '@btc-vision/transaction'; const balances = new AddressMap<bigint>(); balances.set(addr1, 1000n); balances.set(addr2, 2000n); writer.writeAddressValueTuple(balances); ``` ### Output #### getBuffer Get the written data as Uint8Array. ```typescript getBuffer(clear?: boolean): Uint8Array ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `clear` | `boolean` | `true` | Reset writer after getting buffer | ```typescript const data: Uint8Array = writer.getBuffer(); // Writer is now cleared const data2: Uint8Array = writer.getBuffer(false); // Writer retains state ``` #### toBytesReader Convert to a BinaryReader for immediate reading. ```typescript toBytesReader(): BinaryReader ``` ```typescript const reader: BinaryReader = writer.toBytesReader(); ``` ### Utility Methods #### getOffset Get current write position. ```typescript getOffset(): number ``` #### setOffset Set write position. ```typescript setOffset(offset: number): void ``` #### reset Reset writer to initial state. ```typescript reset(): void ``` #### clear Clear buffer and reset offset. ```typescript clear(): void ``` #### allocSafe Ensure buffer has space for additional bytes. ```typescript allocSafe(size: number): void ``` --- ## BinaryReader Reads binary data by sequentially extracting typed values. ### Constructor ```typescript const reader = new BinaryReader(bytes: Uint8Array); ``` ```typescript const data = new Uint8Array([0x00, 0x01, 0x02]); const reader = new BinaryReader(data); ``` ### Primitive Methods #### readU8 Read an unsigned 8-bit integer. ```typescript readU8(): number ``` ```typescript const value: number = reader.readU8(); ``` #### readU16 Read an unsigned 16-bit integer. ```typescript readU16(be?: boolean): number ``` ```typescript const value: number = reader.readU16(); // Big-endian const value2: number = reader.readU16(false); // Little-endian ``` #### readU32 Read an unsigned 32-bit integer. ```typescript readU32(be?: boolean): number ``` #### readU64 Read an unsigned 64-bit integer as bigint. ```typescript readU64(be?: boolean): bigint ``` ```typescript const value: bigint = reader.readU64(); ``` #### readU128 Read an unsigned 128-bit integer as bigint. ```typescript readU128(be?: boolean): bigint ``` #### readU256 Read an unsigned 256-bit integer as bigint. ```typescript readU256(be?: boolean): bigint ``` ```typescript // Read a token amount const amount: bigint = reader.readU256(); ``` #### readI128 Read a signed 128-bit integer as bigint. ```typescript readI128(be?: boolean): bigint ``` #### readBoolean Read a boolean (u8 != 0). ```typescript readBoolean(): boolean ``` ```typescript const flag: boolean = reader.readBoolean(); ``` #### readSelector Read a 4-byte function selector. ```typescript readSelector(): number ``` ```typescript const selector: number = reader.readSelector(); ``` ### String Methods #### readString Read a string of known length. ```typescript readString(length: number): string ``` ```typescript const text: string = reader.readString(5); // Read 5 bytes as string ``` #### readStringWithLength Read a string with u32 length prefix. ```typescript readStringWithLength(be?: boolean): string ``` ```typescript const text: string = reader.readStringWithLength(); ``` ### Bytes Methods #### readBytes Read raw bytes of known length. ```typescript readBytes(length: number, zeroStop?: boolean): Uint8Array ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `length` | `number` | Required | Number of bytes to read | | `zeroStop` | `boolean` | `false` | Stop at null byte | ```typescript const data: Uint8Array = reader.readBytes(32); ``` #### readBytesWithLength Read bytes with u32 length prefix. ```typescript readBytesWithLength(maxLength?: number, be?: boolean): Uint8Array ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `maxLength` | `number` | `0` | Maximum allowed length (0 = unlimited) | | `be` | `boolean` | `true` | Big-endian | ```typescript const data: Uint8Array = reader.readBytesWithLength(); ``` ### Address Methods #### readAddress Read an OPNet address (32 bytes). ```typescript readAddress(): Address ``` ```typescript import { Address } from '@btc-vision/transaction'; const address: Address = reader.readAddress(); console.log(address.toHex()); ``` ### Array Methods #### readU8Array Read an array of u8 values. ```typescript readU8Array(): number[] ``` #### readU16Array Read an array of u16 values. ```typescript readU16Array(be?: boolean): number[] ``` #### readU32Array Read an array of u32 values. ```typescript readU32Array(be?: boolean): number[] ``` #### readU64Array Read an array of u64 values. ```typescript readU64Array(be?: boolean): bigint[] ``` #### readU128Array Read an array of u128 values. ```typescript readU128Array(be?: boolean): bigint[] ``` #### readU256Array Read an array of u256 values. ```typescript readU256Array(be?: boolean): bigint[] ``` ```typescript const amounts: bigint[] = reader.readU256Array(); ``` #### readAddressArray Read an array of addresses. ```typescript readAddressArray(be?: boolean): Address[] ``` ```typescript const addresses: Address[] = reader.readAddressArray(); ``` #### readStringArray Read an array of strings. ```typescript readStringArray(be?: boolean): string[] ``` #### readBytesArray Read an array of byte arrays. ```typescript readBytesArray(be?: boolean): Uint8Array[] ``` #### readArrayOfBuffer Read an array of buffers. ```typescript readArrayOfBuffer(be?: boolean): Uint8Array[] ``` ### Map Methods #### readAddressValueTuple Read a map of addresses to u256 values. ```typescript readAddressValueTuple(be?: boolean): AddressMap<bigint> ``` ```typescript import { AddressMap } from '@btc-vision/transaction'; const balances: AddressMap<bigint> = reader.readAddressValueTuple(); for (const [address, amount] of balances) { console.log(`${address.toHex()}: ${amount}`); } ``` ### Utility Methods #### length Get total buffer length. ```typescript length(): number ``` #### bytesLeft Get remaining unread bytes. ```typescript bytesLeft(): number ``` ```typescript while (reader.bytesLeft() > 0) { // Read more data } ``` #### getOffset Get current read position. ```typescript getOffset(): number ``` #### setOffset Set read position. ```typescript setOffset(offset: number): void ``` ```typescript reader.setOffset(0); // Reset to beginning ``` #### setBuffer Replace the data. ```typescript setBuffer(bytes: Uint8Array): void ``` #### verifyEnd Verify buffer has enough bytes. ```typescript verifyEnd(size: number): void ``` ### Static Comparison Methods ```typescript // String comparison BinaryReader.stringCompare(a: string, b: string): number // BigInt comparison BinaryReader.bigintCompare(a: bigint, b: bigint): number // Number comparison BinaryReader.numberCompare(a: number, b: number): number ``` --- ## Complete Examples ### Building Contract Calldata ```typescript import { BinaryWriter, Address } from '@btc-vision/transaction'; function encodeTransfer(recipient: Address, amount: bigint): Uint8Array { const writer = new BinaryWriter(); // Write function selector (transfer) writer.writeSelector(0xa9059cbb); // Write recipient address writer.writeAddress(recipient); // Write amount writer.writeU256(amount); return writer.getBuffer(); } // Usage const recipient: Address = Address.fromString('0x...'); const amount: bigint = 100_00000000n; // 100 tokens const calldata: Uint8Array = encodeTransfer(recipient, amount); ``` ### Decoding Contract Response ```typescript import { BinaryReader, Address } from '@btc-vision/transaction'; interface TokenInfo { name: string; symbol: string; decimals: number; totalSupply: bigint; } function decodeTokenInfo(data: Uint8Array): TokenInfo { const reader = new BinaryReader(data); return { name: reader.readStringWithLength(), symbol: reader.readStringWithLength(), decimals: reader.readU8(), totalSupply: reader.readU256(), }; } // Usage const response: Uint8Array = /* contract response */; const info: TokenInfo = decodeTokenInfo(response); console.log(`${info.name} (${info.symbol}): ${info.totalSupply}`); ``` ### Batch Transfer Encoding ```typescript import { BinaryWriter, Address } from '@btc-vision/transaction'; interface TransferBatch { recipients: Address[]; amounts: bigint[]; } function encodeBatchTransfer(batch: TransferBatch): Uint8Array { if (batch.recipients.length !== batch.amounts.length) { throw new Error('Recipients and amounts must match'); } const writer = new BinaryWriter(); // Write selector for batchTransfer writer.writeSelector(0x12345678); // Write arrays writer.writeAddressArray(batch.recipients); writer.writeU256Array(batch.amounts); return writer.getBuffer(); } ``` ### Round-Trip Serialization ```typescript import { BinaryWriter, BinaryReader, Address } from '@btc-vision/transaction'; interface Order { id: bigint; maker: Address; taker: Address; amount: bigint; price: bigint; active: boolean; } function serializeOrder(order: Order): Uint8Array { const writer = new BinaryWriter(); writer.writeU64(order.id); writer.writeAddress(order.maker); writer.writeAddress(order.taker); writer.writeU256(order.amount); writer.writeU256(order.price); writer.writeBoolean(order.active); return writer.getBuffer(); } function deserializeOrder(data: Uint8Array): Order { const reader = new BinaryReader(data); return { id: reader.readU64(), maker: reader.readAddress(), taker: reader.readAddress(), amount: reader.readU256(), price: reader.readU256(), active: reader.readBoolean(), }; } // Test round-trip const original: Order = { id: 12345n, maker: Address.fromString('0x...'), taker: Address.fromString('0x...'), amount: 1000n, price: 50n, active: true, }; const serialized: Uint8Array = serializeOrder(original); const deserialized: Order = deserializeOrder(serialized); ``` ### Deployment Constructor Data ```typescript import { BinaryWriter } from '@btc-vision/transaction'; function encodeOP20Constructor( name: string, symbol: string, decimals: number, maxSupply: bigint ): Uint8Array { const writer = new BinaryWriter(); writer.writeStringWithLength(name); writer.writeStringWithLength(symbol); writer.writeU8(decimals); writer.writeU256(maxSupply); return writer.getBuffer(); } // Usage const constructorData: Uint8Array = encodeOP20Constructor( 'My Token', 'MTK', 8, 21_000_000_00000000n ); ``` --- ## Type Sizes | Type | Size (bytes) | Range | |------|--------------|-------| | `u8` | 1 | 0 to 255 | | `u16` | 2 | 0 to 65,535 | | `u32` | 4 | 0 to 4,294,967,295 | | `u64` | 8 | 0 to 18,446,744,073,709,551,615 | | `u128` | 16 | 0 to 340,282,366,920,938,463,463,374,607,431,768,211,455 | | `u256` | 32 | 0 to 2^256 - 1 | | `i128` | 16 | -2^127 to 2^127 - 1 | | `Address` | 32 | OPNet address | | `Selector` | 4 | Function selector | --- ## Best Practices 1. **Match Write/Read Order**: Always read data in the same order it was written 2. **Use Length Prefixes**: For variable-length data, use `writeStringWithLength` / `readStringWithLength` 3. **Check Remaining Bytes**: Use `bytesLeft()` to verify data availability 4. **Handle Endianness**: Default is big-endian; use `be=false` for little-endian 5. **Type Safety**: Use explicit types for all values ```typescript // Good const amount: bigint = reader.readU256(); const count: number = reader.readU32(); // Bad - implicit types const amount = reader.readU256(); const count = reader.readU32(); ``` 6. **Error Handling**: Wrap reads in try-catch for malformed data ```typescript try { const value: bigint = reader.readU256(); } catch (error: unknown) { console.error('Failed to read value:', error); } ``` --- ## Next Steps - [Bitcoin Utils](./bitcoin-utils.md) - Formatting utilities - [Revert Decoder](./revert-decoder.md) - Decoding error messages - [Contract Deployment](../examples/deployment-examples.md) - Using BinaryWriter for deployment --- [← Previous: Bitcoin Utils](./bitcoin-utils.md) | [Next: Revert Decoder →](./revert-decoder.md)