Skip to content

Commit ddf86a9

Browse files
authored
docs: point the Uint8Array migration guide at SDK byte helpers (#1661)
* fix(xdr): add per-format overloads and jsdoc to encodeBytes/decodeBytes
1 parent 1c83252 commit ddf86a9

2 files changed

Lines changed: 81 additions & 14 deletions

File tree

docs/UINT8ARRAY_MIGRATION.md

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,48 @@ etc. on an SDK result, that code needs updating.
2323

2424
## 1. Recipes: replacing Buffer methods on SDK results
2525

26-
The [`uint8array-extras`](https://github.com/sindresorhus/uint8array-extras)
27-
package (which the SDK itself uses) covers most of these; plain `DataView`
28-
covers the rest. In Node you can also just wrap the result:
29-
`Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)`.
26+
The SDK ships the hex and base64 conversions itself: `xdr.encodeBytes` and
27+
`xdr.decodeBytes` (the same functions behind every `toXdr` / `fromXdr` call).
28+
XDR values need no helper at all, since `toXdr` takes a format directly. Cases
29+
the SDK does not cover (`equals`, `concat`, `compare`, UTF-8) are handled by
30+
[`uint8array-extras`](https://github.com/sindresorhus/uint8array-extras) or
31+
plain `DataView`, as noted per row.
32+
33+
```ts
34+
import { xdr } from "@stellar/stellar-sdk";
35+
36+
xdr.encodeBytes(tx.hash(), "hex"); // "deadbeef…"
37+
xdr.encodeBytes(keypair.sign(data), "base64");
38+
xdr.decodeBytes("deadbeef", "hex"); // Uint8Array
39+
40+
// XDR values encode directly — no helper needed:
41+
scVal.toXdr("hex");
42+
entry.toXdr("base64");
43+
```
3044

3145
| Before (Buffer) | After (Uint8Array) |
3246
| ---------------------------------- | ----------------------------------------------------------- |
33-
| `buf.toString("hex")` | `uint8ArrayToHex(bytes)` |
34-
| `buf.toString("base64")` | `uint8ArrayToBase64(bytes)` |
35-
| `buf.toString("utf8")` / `.toString()` | `uint8ArrayToString(bytes)` |
36-
| `Buffer.from(hex, "hex")` | `hexToUint8Array(hex)` |
37-
| `Buffer.from(b64, "base64")` | `base64ToUint8Array(b64)` |
38-
| `Buffer.from(str)` (UTF-8) | `stringToUint8Array(str)` |
39-
| `Buffer.concat([a, b])` | `concatUint8Arrays([a, b])` |
40-
| `a.equals(b)` | `areUint8ArraysEqual(a, b)` |
41-
| `a.compare(b)` | `compareUint8Arrays(a, b)` |
47+
| `xdrVal.toXDR("hex")` / `("base64")` | `xdrVal.toXdr("hex")` / `("base64")` — returns `string`, no helper needed |
48+
| `buf.toString("hex")` | `xdr.encodeBytes(bytes, "hex")` |
49+
| `buf.toString("base64")` | `xdr.encodeBytes(bytes, "base64")` |
50+
| `Buffer.from(hex, "hex")` | `xdr.decodeBytes(hex, "hex")` |
51+
| `Buffer.from(b64, "base64")` | `xdr.decodeBytes(b64, "base64")` |
52+
| `buf.toString("utf8")` / `.toString()` | `uint8ArrayToString(bytes)` (`uint8array-extras`) |
53+
| `Buffer.from(str)` (UTF-8) | `stringToUint8Array(str)` (`uint8array-extras`) |
54+
| `Buffer.concat([a, b])` | `concatUint8Arrays([a, b])` (`uint8array-extras`) |
55+
| `a.equals(b)` | `areUint8ArraysEqual(a, b)` (`uint8array-extras`) |
56+
| `a.compare(b)` | `compareUint8Arrays(a, b)` (`uint8array-extras`) |
4257
| `Buffer.alloc(n)` | `new Uint8Array(n)` |
4358
| `Buffer.isBuffer(x)` | `x instanceof Uint8Array` |
4459
| `buf.readUInt32BE(o)` | `new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(o)` |
4560
| `buf.readBigUInt64BE(o)` | `…same DataView….getBigUint64(o)` |
4661
| `buf.slice(a, b)` (view) | `bytes.subarray(a, b)`. Note that `Uint8Array.prototype.slice` **copies**, while `Buffer.prototype.slice` returned a view |
4762

63+
As a stopgap in Node you can wrap a result back into a Buffer:
64+
`Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)`. Treat that as
65+
temporary; it reintroduces the Buffer dependency v17 removed, and it does not
66+
work in browsers without a polyfill.
67+
4868
Three semantic traps to check for:
4969

5070
- `.toString("hex")` fails silently. `Uint8Array.prototype.toString` ignores its
@@ -62,7 +82,7 @@ Three semantic traps to check for:
6282
result. Normalize one side, or compare encodings:
6383

6484
```ts
65-
expect(uint8ArrayToHex(actual)).toBe(expectedHex); // preferred
85+
expect(xdr.encodeBytes(actual, "hex")).toBe(expectedHex); // preferred
6686
expect(Array.from(actual)).toEqual(Array.from(expectedBuffer));
6787
```
6888

src/xdr/values/xdr-value.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,33 @@ function splitArrayArgs(
281281
: [undefined, formatOrOptions ?? maybeOptions ?? {}];
282282
}
283283

284+
/**
285+
* Encode raw bytes into the requested {@link XdrFormat}: the bytes themselves
286+
* for `"raw"`, or a `"hex"` / `"base64"` string. This is the encoder behind
287+
* every `toXdr(format)` call, exported so consumers can format any
288+
* `Uint8Array` the SDK hands back (hashes, signatures, raw keys) without a
289+
* helper library.
290+
*
291+
* ```ts
292+
* encodeBytes(new Uint8Array([0xde, 0xad, 0xbe, 0xef]), "hex"); // "deadbeef"
293+
* encodeBytes(new Uint8Array([0xde, 0xad, 0xbe, 0xef]), "base64"); // "3q2+7w=="
294+
* ```
295+
*
296+
* @param bytes - the bytes to encode
297+
* @param format - `"raw"` returns `bytes` unchanged; `"hex"` and `"base64"`
298+
* return a string
299+
* @throws an {@link XdrError} on an unknown format
300+
* @see {@link decodeBytes} for the reverse direction
301+
*/
302+
export function encodeBytes(bytes: Uint8Array, format: "raw"): Uint8Array;
303+
export function encodeBytes(
304+
bytes: Uint8Array,
305+
format: "hex" | "base64",
306+
): string;
307+
export function encodeBytes(
308+
bytes: Uint8Array,
309+
format: XdrFormat,
310+
): Uint8Array | string;
284311
export function encodeBytes(
285312
bytes: Uint8Array,
286313
format: XdrFormat,
@@ -299,6 +326,26 @@ export function encodeBytes(
299326
}
300327
}
301328

329+
/**
330+
* Decode a `"hex"` or `"base64"` string into bytes; a `Uint8Array` input
331+
* passes through unchanged. This is the decoder behind every
332+
* `fromXdr(input, format)` call, exported so consumers can parse encoded
333+
* byte strings without a helper library.
334+
*
335+
* ```ts
336+
* decodeBytes("deadbeef", "hex"); // Uint8Array [0xde, 0xad, 0xbe, 0xef]
337+
* decodeBytes("3q2+7w==", "base64");
338+
* ```
339+
*
340+
* Decoding is strict: malformed input throws instead of being silently
341+
* truncated the way `Buffer.from(str, "hex")` was.
342+
*
343+
* @param input - the bytes or encoded string to decode
344+
* @param format - required when `input` is a string; ignored for `Uint8Array`
345+
* @throws an {@link XdrError} when a string arrives without a `"hex"` /
346+
* `"base64"` format, or the format is unknown
347+
* @see {@link encodeBytes} for the reverse direction
348+
*/
302349
export function decodeBytes(
303350
input: Uint8Array | string,
304351
format: "raw" | "hex" | "base64" | undefined,

0 commit comments

Comments
 (0)