WizardConnect/docs/serialization.md

133 lines
4.7 KiB
Markdown
Raw Normal View History

# 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.