133 lines
4.7 KiB
Markdown
133 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.
|