Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions docs/guides/flutter-web-bigint.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,75 @@ cannot be represented safely as a JavaScript `Number`.
Precision loss in a muxed ID can route funds or metadata to the wrong account
context. Treat muxed IDs as exact integers, not floating-point-compatible
numbers.

## The Built-In Safety Net (v1.1.0+)

Since v1.1.0 the package ships a dedicated web-safe layer, so you do not have
to hand-roll these guards:

### `isWebJsRuntime` β€” conditional compilation probe

```dart
import 'package:stellar_address_kit/stellar_address_kit.dart';

if (isWebJsRuntime) {
// Compiled to JavaScript: Dart `int` is a JS `Number` here.
}
```

The flag is resolved at compile time via conditional imports
(`dart.library.html`), not by a runtime check β€” there is no
`dart:io`/`dart:html` dependency in your app.

### `SafeRoutingId` β€” the BigInt wrapper

`SafeRoutingId` parses, validates, compares, and serializes 64-bit routing
IDs as exact decimal strings backed by `BigInt`. It never converts through
`int`/JS `Number`, so the full uint64 range survives on Flutter Web:

```dart
final id = SafeRoutingId.parse('9007199254740993'); // 2^53 + 1
id.value; // '9007199254740993' β€” exact on web
id.toBigInt; // exact BigInt
id.toJson(); // '9007199254740993' β€” safe for jsonEncode
id.isJsSafe; // false β€” exceeds Number.MAX_SAFE_INTEGER
id.exceedsJsSafeRange; // true
```

`SafeRoutingId.fromInt` **refuses** values above `Number.MAX_SAFE_INTEGER`
when `isWebJsRuntime` is true instead of propagating an already-truncated
JS `Number` β€” parse from the original string with `SafeRoutingId.parse`.

### Web-safe accessors on `RoutingResult`

```dart
final result = extractRoutingSync(RoutingInput(
destination: 'GAYCUYT…',
memoType: 'id',
memoValue: '9007199254740993',
));

result.id; // BigInt β€” exact on every platform
result.idString; // '9007199254740993' β€” web-safe string form
result.safeId; // SafeRoutingId wrapper
```

Internally, `extractRoutingSync` and the MEMO_ID / MEMO_TEXT normalizers
validate uint64 range on the decimal string itself (length and lexicographic
comparison), never via `int`, so browser builds cannot silently truncate
massive routing IDs during parsing.

## Web Test Vectors

The browser-only suite
`packages/core-dart/test/web_compat/routing_id_web_test.dart` pins the
boundary IDs `2^53 - 1`, `2^53`, `2^53 + 1`, `2^63 - 1`, `2^63`, and
`2^64 - 1` (plus `0` and `1`) through memo extraction, muxed encode/decode,
and JSON serialization, including the canary proving that `int.parse`
truncates `9007199254740993` β†’ `9007199254740992` in a browser while
`SafeRoutingId` keeps it exact. Run it with:

```bash
cd packages/core-dart
dart test test/web_compat --platform chrome
```
24 changes: 23 additions & 1 deletion docs/guides/flutter-web-bigint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,33 @@ description: "Avoiding precision loss in the browser."
Flutter Web compiles to JavaScript, which means it shares the same 64-bit float limitations as any other JS application.

## The Problem
If you parse a large Memo ID (near `18,446,744,073,709,551,615`) as an `int` in Dart when running on Web, you will lose precision.
If you parse a large Memo ID (near `18,446,744,073,709,551,615`) as an `int` in Dart when running on Web, you will lose precision. Dart `int` compiles to a JS `Number`, which is only exact up to `Number.MAX_SAFE_INTEGER` (`9,007,199,254,740,991`). For example, `int.parse('9007199254740993')` silently becomes `9007199254740992` β€” one unit off, with no error.

## The Solution
The Dart package uses `BigInt` internally and returns the `routingId` as a `String`.

<Warning>
Never convert the `routingId` to an `int` if your app targets Web. Always keep it as a `String` or a `BigInt`.
</Warning>

## The Built-In Safety Net (v1.1.0+)

The package now ships a dedicated web-safe layer so you do not have to hand-roll these guards:

- **`isWebJsRuntime`** β€” a compile-time (conditional-import) probe that is `true` only when your app is compiled to JavaScript.
- **`SafeRoutingId`** β€” a BigInt-backed wrapper that parses, validates, compares, and serializes 64-bit routing IDs as exact decimal strings. It never routes the value through `int`/JS `Number`, and `fromInt` rejects values above `Number.MAX_SAFE_INTEGER` on web builds instead of propagating a truncated number.
- **`RoutingResult.idString` / `RoutingResult.safeId`** β€” web-safe accessors for the resolved routing ID.

```dart
final id = SafeRoutingId.parse('9007199254740993'); // 2^53 + 1
id.value; // '9007199254740993' β€” exact in the browser
id.toJson(); // '9007199254740993' β€” safe to jsonEncode
id.exceedsJsSafeRange; // true
```

Browser-only test vectors in `packages/core-dart/test/web_compat/routing_id_web_test.dart` pin the boundary IDs (`2^53 - 1`, `2^53`, `2^53 + 1`, `2^63 - 1`, `2^63`, `2^64 - 1`) through memo extraction, muxed decode, and JSON serialization. Run them with:

```bash
cd packages/core-dart
dart test test/web_compat --platform chrome
```
Loading
Loading