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.
132 lines
4.7 KiB
Markdown
132 lines
4.7 KiB
Markdown
# 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:
|
|
|
|
```typescript
|
|
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`:
|
|
|
|
```typescript
|
|
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`:
|
|
|
|
```typescript
|
|
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`:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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.
|