Canonical encoding for BigInt (<bigint: Xn>) and Uint8Array (hex or <Uint8Array: 0x...>) used in the relay protocol. Provides both serialization (sourceOutputToRelay, transactionToHex) and deserialization (parseExtendedJson, toUint8Array, toBigInt) so dapps and wallets don't have to implement this independently.
4.7 KiB
Relay serialization
The WizardConnect relay transmits messages as JSON, which cannot represent BigInt or
Uint8Array natively. @wizardconnect/core provides canonical encoding helpers so dapps
and wallets always agree on the wire format.
The helpers are split into two layers:
- Generic (
serialize.ts) — relay-level type coercion (toUint8Array,toBigInt,parseExtendedJson). Useful for any protocol. - hdwalletv1 (
protocols/hdwalletv1-serialize.ts) — transaction-specific serialization (sourceOutputToRelay,transactionToHex). Tied to the sign-transaction flow.
Encoding conventions
| Native type | Relay format | Example |
|---|---|---|
Uint8Array |
hex string | "76a914...88ac" |
Uint8Array |
extended format (libauth stringify) | "<Uint8Array: 0x76a914...>" |
BigInt |
extended format | "<bigint: 200000n>" |
Both extended formats are accepted by the deserialization helpers. The serialization helpers
produce hex strings for Uint8Array and <bigint: Xn> for BigInt.
hdwalletv1 serialization (dapp → relay)
sourceOutputToRelay(sourceOutput)
Converts a source output with native types to relay-safe JSON:
import { sourceOutputToRelay } from "@wizardconnect/core/hdwalletv1-serialize";
const relayOutput = sourceOutputToRelay({
outpointTransactionHash: txidBytes, // Uint8Array → hex string
outpointIndex: 0, // number (unchanged)
unlockingBytecode: new Uint8Array(0), // Uint8Array → hex string
sequenceNumber: 0xffffffff, // number (unchanged)
valueSatoshis: 200000n, // BigInt → "<bigint: 200000n>"
lockingBytecode: scriptBytes, // Uint8Array → hex string
token: { // optional
category: categoryBytes, // Uint8Array → hex string
amount: 1000n, // BigInt → "<bigint: 1000n>"
},
});
// relayOutput is JSON-serializable (no BigInt, no Uint8Array)
JSON.stringify(relayOutput); // works
transactionToHex(inputs, outputs, version?, locktime?)
Encodes a transaction to hex using libauth's encodeTransaction:
import { transactionToHex } from "@wizardconnect/core/hdwalletv1-serialize";
const txHex = transactionToHex(inputs, outputs);
// txHex is a hex string ready for the relay
Note: libauth's encodeTransaction reverses outpointTransactionHash to wire format
internally. Pass txids in display order (big-endian, as returned by electrum/explorers).
Deserialization (relay → wallet)
toUint8Array(value)
Converts hex strings, extended JSON format, or Uint8Array to Uint8Array:
import { toUint8Array } from "@wizardconnect/core";
toUint8Array("76a914...88ac"); // hex string
toUint8Array("<Uint8Array: 0x76a914...88ac>"); // extended format
toUint8Array(existingBytes); // pass-through
toBigInt(value)
Converts numeric strings, extended JSON format, numbers, or bigint to bigint:
import { toBigInt } from "@wizardconnect/core";
toBigInt("<bigint: 200000n>"); // extended format
toBigInt("200000"); // numeric string
toBigInt(200000); // number
toBigInt(200000n); // pass-through
parseExtendedJson(jsonString)
Parses a full JSON string, converting all extended-format values in one pass:
import { parseExtendedJson } from "@wizardconnect/core";
const obj = parseExtendedJson('{"value":"<bigint: 200000n>","data":"<Uint8Array: 0xab>"}');
// obj.value === 200000n
// obj.data instanceof Uint8Array
isExtendedJsonFormat(str)
Returns true if a string contains <bigint: ...> or <Uint8Array: ...> markers.
Usage in sign requests
A typical dapp builds a sign request like this:
import { RelayMsgAction } from "@wizardconnect/core";
import { sourceOutputToRelay, transactionToHex } from "@wizardconnect/core/hdwalletv1-serialize";
const txHex = transactionToHex(inputs, outputs);
const sourceOutputs = inputs.map((input, i) =>
sourceOutputToRelay({
...input,
valueSatoshis: utxos[i].value,
lockingBytecode: utxos[i].script,
})
);
const signReq = {
action: RelayMsgAction.SignTransactionRequest,
time: Math.floor(Date.now() / 1000),
sequence: manager.nextSequence(),
transaction: { transaction: txHex, sourceOutputs, broadcast: false },
inputPaths: [[0, "receive", 0]],
};
The wallet deserializes using toUint8Array and toBigInt on the received fields.