Open source · pure Dart · three dependencies

FLARE,
IN DART.

Flare publishes developer guides for JavaScript, React, Python, Rust and Go. There is no Dart guide: querying Flare's own documentation search for dart or flutter returns "No matching documents found". This repository is that missing piece.

main.dart

import 'package:flare_network/flare_network.dart';

Future<void> main() async {
  final client = FlareClient(FlareChain.coston2);
  try {
    final ftso = await FtsoV2.resolve(client);
    final ids = [Feeds.flrUsd, Feeds.btcUsd];
    for (final f in await ftso.getFeedsById(ids)) {
      print('${f.feedId.name}: ${f.toDecimalString()}');
    }
  } finally {
    client.close();
  }
}
Output
FLR/USD: 0.00626973
BTC/USD: 62887.17

BEING PRECISE ABOUT
WHAT WAS MISSING.

Because Flare is EVM compatible, and a reviewer will check.

A Flutter app could always reach Flare over JSON-RPC, and web3dart is a maintained, general purpose Dart EVM library that works against any Flare RPC. That has never been the gap.

What did not exist is anything Flare specific: typed, generated bindings for Flare's own contracts, so that FTSO feeds, FDC attestations, FAssets minting and Smart Accounts are reachable without hand-rolling ABIs against the registry.

That is the difference between a week of work and an import.

FOUR PACKAGES.
164 BINDINGS.

Generated from Flare's own ABI artifacts, so they do not drift by hand.

PackageWhat it is
flare_networkThe core SDK. Pure Dart, three dependencies, no Flutter, no FFI
flare_network_periphery164 generated bindings: 1,049 read methods, 513 transaction builders, 592 events, 168 custom errors
flare_network_codegenDev-only CLI. Flare's ABI artifacts into typed Dart
examples/developer-hub-dart17 runnable examples, mirroring Flare's own Go, Python and Rust sets

IT HOLDS NO KEYS,
AND NEVER WILL.

A deliberate, permanent non-goal. It does everything on either side of the signature.

final wnat = await IWNatContract.resolve(client);

// Build. Only payable functions accept a value, so attaching one to a
// function that would reject it does not compile.
final tx = wnat.depositTx(value: BigInt.from(10).pow(18), from: user);

// Price. Simulates against current state, so a doomed action fails here,
// before the user is asked to approve it and before it costs anything.
final ready = await client.prepareTransaction(tx);
print(ready.maxCost);            // worst case, in wei

// Sign, with your wallet library. Not this package.
final hash = await wallet.request('eth_sendTransaction', [ready.toWalletJson()]);

// Confirm. A mined transaction is not a successful one.
final receipt = await client.waitForReceipt(hash);
if (!receipt.succeeded) { /* reverted, and still cost gas */ }

If it reverts after it is mined, the receipt will not tell you why: it has no field for it. Replaying the call at its own block does.

if (!receipt.succeeded) {
  final why = await client.explainRevert(receipt);
  print(why?.description); // "ERC20: transfer amount exceeds balance"
}

When something reverts before signing, you get the reason directly. That matters on Flare specifically, because 168 of the custom errors in the published ABIs carry no message at all, so the node reports them as four opaque bytes.

try {
  await client.prepareTransaction(tx);
} on FlareRpcException catch (e) {
  print(IAssetManagerContract.decodeRevert(e)?.description);
  // e.g. "InsufficientFundsForRedeem(1000000)"
}
Flow chart. Build, then price against live state. If it would revert, decodeRevert names it. If not, the user signs with their own wallet, and after the receipt arrives explainRevert replays a failed call at its own block, otherwise the typed logs are decoded.
Everything except the signature. Both failure paths are the point. Before signing you get a name for the revert; after mining you get one too, because the receipt has no field that carries it.

XRPL HOLDERS,
WITHOUT A FLARE KEY.

Smart Accounts gives an XRP holder a Flare account they control from XRPL. No FLR for gas, no Flare key. Live on mainnet and on Coston2.

final accounts = await SmartAccountsClient.resolve(client);
final account = await accounts.accountFor('rLDkBYohbZw1AuFnpYtAcq8sbMjjBWKvE4');

// `address` is derived, not looked up. It is non-zero even for an XRPL
// address that has never existed. `isDeployed` is the field that matters.
if (account.isDeployed) {
  final held = await accounts.balancesOf(account.address);
  print(held.fXrp.balance);
  print(held.heldVaults);   // not `vaults`, which lists every vault, held or not
}

Those two naming choices are the whole point of a typed binding. An address that is derived rather than looked up is non-zero for an account that does not exist, and a list called vaults that means "all vaults" rather than "your vaults" is a bug waiting for somebody's balance display.

Flow chart. An XRPL address with no FLR and no Flare key resolves through accountFor to a derived Flare address, which is non-zero whether or not it exists. Only isDeployed tells you there is anything there, and then balancesOf returns heldVaults rather than every vault.
Derived, not looked up. The address comes back non-zero for an XRPL account that has never existed, so the field that answers "is there anything there" is isDeployed, never the address.

331 TESTS.
91 AGAINST THE CHAIN.

Runs on all six platforms Dart targets, including web, and pana reports the package WASM ready.

Working today

Against live networks.

Contract resolution, FTSOv2 price feeds, Scaling anchor feeds with Merkle proofs, FDC attestations, FAssets, event log decoding, WebSocket subscriptions, arbitrary eth_call, and the transaction path above.

Out of scope

P-chain staking and C to P transfers.

They are not EVM transactions. Hardware wallets that only sign are served by sendRawTransaction, which broadcasts for them.

Verify it

Two commands.

dart test runs 240 hermetic, offline. dart test -P integration runs 91 against live Coston2.

cd packages/flare_network
dart test                  # 240 tests, hermetic, offline
dart test -P integration   # 91 tests against live Coston2