Fliin
On this page

Screens / commerce

Crypto swap

Lets the user swap one crypto for another: the amount sent and received, a button that flips them, the rate and its countdown, fees and slippage, then a confirmation sheet and a sent state.

16 of 16 variants pass
Crypto swap, ready state
Platform
Brightness
Text scale
Direction

Import

Crypto swap is part of Fliin Pro. With a license key, sign in once with fliin login, then add it in a Flutter app where fliin init has run:

fliin login
fliin add crypto-swap

Also installs: app-bar, avatar, button, core, empty-state, icons, menu, page, progress, segmented, sheet, skeleton, surface

Examples

Ready

The exchange of a crypto wallet: 1.04 BTC for ether on a band in the theme's accent, the quote counting down and renewed when it runs out. Half and Max set the amount, the round button swaps the two assets, the sliders in the bar set the slippage tolerance, and "Review exchange" opens the confirmation. standalone holds the screen over the SwapService you pass.

Crypto swap, ready state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapReadyExample extends StatelessWidget {
  const CryptoSwapReadyExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: sampleSwapReady(age: Duration.zero, assets: sampleSwapAssetsWith(_logo)),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Insufficient

An amount above the balance: the screen says how much the wallet holds, and the button waits.

Crypto swap, insufficient state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapInsufficientExample extends StatelessWidget {
  const CryptoSwapInsufficientExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: sampleSwapReady(amount: 240000000, age: Duration.zero, assets: sampleSwapAssetsWith(_logo)),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Minimum

An amount below the asset's minimum: the screen says the minimum, and the button waits.

Crypto swap, minimum state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapMinimumExample extends StatelessWidget {
  const CryptoSwapMinimumExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: sampleSwapReady(amount: 10000, age: Duration.zero, assets: sampleSwapAssetsWith(_logo)),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Expired

A quote that ran out and could not be renewed: "Refresh" asks for a new one.

Crypto swap, expired state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapExpiredExample extends StatelessWidget {
  const CryptoSwapExpiredExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: sampleSwapReady(expired: true, assets: sampleSwapAssetsWith(_logo)),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Confirm

The confirmation, which the screen turns into while the user reviews: what is sent on the band, what arrives at least, the quote's details and how long it stays valid in the sheet. "Confirm exchange" sends the order; back and "Cancel" return to the form.

Crypto swap, confirm state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapConfirmExample extends StatelessWidget {
  const CryptoSwapConfirmExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: sampleSwapReady(reviewing: true, age: Duration.zero, assets: sampleSwapAssetsWith(_logo)),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Sent

Once the service accepted the exchange: back to the wallet, or the exchange's details.

Crypto swap, sent state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_controller.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapSentExample extends StatelessWidget {
  const CryptoSwapSentExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: SwapSent(sampleSwapReceipt),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Loading

While the wallet and the first quote load: the skeleton of the form.

Crypto swap, loading state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_controller.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapLoadingExample extends StatelessWidget {
  const CryptoSwapLoadingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: const SwapLoading(),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Empty

A wallet with nothing to exchange: the screen offers to add crypto.

Crypto swap, empty state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_controller.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapEmptyExample extends StatelessWidget {
  const CryptoSwapEmptyExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: const SwapEmpty(),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Error

When loading fails, the screen says so and offers to try again.

Crypto swap, error state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_controller.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapErrorExample extends StatelessWidget {
  const CryptoSwapErrorExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: const SwapFailed(),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Offline

Without a network, the screen says so and offers to try again.

Crypto swap, offline state
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_controller.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapOfflineExample extends StatelessWidget {
  const CryptoSwapOfflineExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CryptoSwapScreen.standalone(
      service: SampleSwapService(wallet: sampleSwapAssetsWith(_logo), latency: const Duration(milliseconds: 900)),
      initial: const SwapOffline(),
      onBack: () {},
      onDone: () {},
      onViewDetails: (_) {},
      onAddFunds: () {},
    );
  }
}

/// The sample's logos, from the app's assets, by symbol.
ImageProvider _logo(String symbol) => AssetImage('images/crypto/${symbol.toLowerCase()}.png');

Routing

fliin add crypto-swap prints the route for the router it finds in your pubspec, a file next to the page that wires its navigation. Each of them:

go_router

// lib/crypto_swap_route.dart, next to crypto_swap_page.dart.
import 'package:go_router/go_router.dart';

import 'crypto_swap_page.dart';

/// Add cryptoSwapRoute to the routes of your GoRouter.
final cryptoSwapRoute = GoRoute(
  path: '/exchange',
  builder: (context, state) => CryptoSwapPage(
    // Alone in the stack (deep link, go): your app's home.
    onBack: () => context.canPop() ? context.pop() : context.go('/'),
  ),
);

navigator

// lib/crypto_swap_route.dart, next to crypto_swap_page.dart.
import 'package:flutter/material.dart';

import 'crypto_swap_page.dart';

/// Opens “Crypto swap”: `Navigator.of(context).push(cryptoSwapRoute())`.
Route<void> cryptoSwapRoute() => MaterialPageRoute<void>(
  builder: (context) => CryptoSwapPage(
    onBack: () => Navigator.of(context).maybePop(),
  ),
);

State

The controller is a ChangeNotifier from widgets.dart, with no package. fliin add crypto-swap prints the page that holds it with the state management it finds, its imports written for your package; below they read your_app. Your backend implements the Service interface of the screen's _data.dart file:

none

// lib/crypto_swap_page.dart
// Your backend: implement SwapService (crypto_swap_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';

class CryptoSwapPage extends StatelessWidget {
  const CryptoSwapPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => CryptoSwapScreen.standalone(
    service: const SampleSwapService(),
    onBack: onBack,
    // Once the exchange is sent: back to your wallet.
    onDone: onBack,
  );
}

provider

// lib/crypto_swap_page.dart
// Your backend: implement SwapService (crypto_swap_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_controller.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

class CryptoSwapPage extends StatelessWidget {
  const CryptoSwapPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
    create: (_) => CryptoSwapController(service: const SampleSwapService())..load(),
    child: Builder(
      builder: (context) => CryptoSwapScreen(
        controller: context.read<CryptoSwapController>(),
        onBack: onBack,
        // Once the exchange is sent: back to your wallet.
        onDone: onBack,
      ),
    ),
  );
}

riverpod

// lib/crypto_swap_page.dart
// Your backend: implement SwapService (crypto_swap_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_controller.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final cryptoSwapProvider = Provider.autoDispose<CryptoSwapController>((ref) {
  final controller = CryptoSwapController(service: const SampleSwapService())..load();
  ref.onDispose(controller.dispose);
  return controller;
});

class CryptoSwapPage extends ConsumerWidget {
  const CryptoSwapPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context, WidgetRef ref) => CryptoSwapScreen(
    controller: ref.watch(cryptoSwapProvider),
    onBack: onBack,
    // Once the exchange is sent: back to your wallet.
    onDone: onBack,
  );
}

bloc

// lib/crypto_swap_page.dart
// Your backend: implement SwapService (crypto_swap_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_controller.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_data.dart';
import 'package:your_app/ui/fliin/screens/crypto_swap/crypto_swap_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

/// The screen's controller as a bloc, for your BlocListener and BlocBuilder:
/// one emission per controller notification. `version` tells them apart
/// when `state` does not change (mode, filters, text…, read them on
/// `controller`). The screen listens to the controller itself.
class CryptoSwapCubit extends Cubit<({SwapState state, int version})> {
  CryptoSwapCubit(this.controller) : super((state: controller.state, version: 0)) {
    controller.addListener(_changed);
  }

  final CryptoSwapController controller;

  void _changed() => emit((state: controller.state, version: state.version + 1));

  @override
  Future<void> close() {
    controller
      ..removeListener(_changed)
      ..dispose();
    return super.close();
  }
}

class CryptoSwapPage extends StatelessWidget {
  const CryptoSwapPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => BlocProvider(
    create: (_) => CryptoSwapCubit(CryptoSwapController(service: const SampleSwapService())..load()),
    child: Builder(
      builder: (context) => CryptoSwapScreen(
        controller: context.read<CryptoSwapCubit>().controller,
        onBack: onBack,
        // Once the exchange is sent: back to your wallet.
        onDone: onBack,
      ),
    ),
  );
}

API

SwapAssetPicker: typedef = Future<SwapAsset?> Function(SwapSide side, List<SwapAsset> assets)
CryptoSwapScreen: class extends StatefulWidget
CryptoSwapScreen.new: const new({super.key, required CryptoSwapController controller, VoidCallback? onBack, SwapAssetPicker? onPickAsset, VoidCallback? onDone, ValueChanged<SwapReceipt>? onViewDetails, VoidCallback? onAddFunds, SwapFormat? format})
CryptoSwapScreen.standalone: const new({super.key, required SwapService service, SwapState? initial, DateTime Function()? clock, String? from, String? to, VoidCallback? onBack, SwapAssetPicker? onPickAsset, VoidCallback? onDone, ValueChanged<SwapReceipt>? onViewDetails, VoidCallback? onAddFunds, SwapFormat? format})
CryptoSwapScreen.controller: get CryptoSwapController?
CryptoSwapScreen.service: get SwapService?
CryptoSwapScreen.initial: get SwapState?
CryptoSwapScreen.clock: get DateTime Function()?
CryptoSwapScreen.from: get String?
CryptoSwapScreen.to: get String?
CryptoSwapScreen.onBack: get VoidCallback?
CryptoSwapScreen.onPickAsset: get SwapAssetPicker?
CryptoSwapScreen.onDone: get VoidCallback?
CryptoSwapScreen.onViewDetails: get ValueChanged<SwapReceipt>?
CryptoSwapScreen.onAddFunds: get VoidCallback?
CryptoSwapScreen.format: get SwapFormat?
CryptoSwapScreen.createState: State<CryptoSwapScreen>()
SwapState: sealed class permits {SwapEmpty, SwapFailed, SwapLoading, SwapOffline, SwapReady, SwapSent}
SwapState.new: const new()
SwapLoading: final class extends SwapState
SwapLoading.new: const new()
SwapProblem: enum {insufficient, minimum, expired}
SwapProblem.insufficient: static final SwapProblem
SwapProblem.minimum: static final SwapProblem
SwapProblem.expired: static final SwapProblem
SwapNotice: enum {failed, offline}
SwapNotice.failed: static final SwapNotice
SwapNotice.offline: static final SwapNotice
SwapReady: final class extends SwapState
SwapReady.new: const new({required List<SwapAsset> assets, required SwapAsset from, required SwapAsset to, int amount, SwapQuote? quote, SwapSlippage slippage, bool quoting, bool expired, bool reviewing, bool sending, SwapNotice? notice})
SwapReady.assets: get List<SwapAsset>
SwapReady.from: get SwapAsset
SwapReady.to: get SwapAsset
SwapReady.amount: get int
SwapReady.quote: get SwapQuote?
SwapReady.slippage: get SwapSlippage
SwapReady.quoting: get bool
SwapReady.expired: get bool
SwapReady.reviewing: get bool
SwapReady.sending: get bool
SwapReady.notice: get SwapNotice?
SwapReady.receive: get int?
SwapReady.problem: get SwapProblem?
SwapReady.canReview: get bool
SwapEmpty: final class extends SwapState
SwapEmpty.new: const new()
SwapFailed: final class extends SwapState
SwapFailed.new: const new()
SwapOffline: final class extends SwapState
SwapOffline.new: const new()
SwapSent: final class extends SwapState
SwapSent.new: const new(SwapReceipt receipt)
SwapSent.receipt: get SwapReceipt
CryptoSwapController: class extends ChangeNotifier
CryptoSwapController.new: new({required SwapService service, SwapState initial, DateTime Function()? clock, String? from, String? to})
CryptoSwapController.service: get SwapService
CryptoSwapController.clock: get DateTime Function()
CryptoSwapController.from: get String?
CryptoSwapController.to: get String?
CryptoSwapController.state: get SwapState
CryptoSwapController.remaining: get Duration
CryptoSwapController.remainingShare: get double
CryptoSwapController.load: Future<void>()
CryptoSwapController.refresh: Future<void>()
CryptoSwapController.setAmount: void(int units)
CryptoSwapController.useHalf: void()
CryptoSwapController.useMax: void()
CryptoSwapController.flip: Future<void>()
CryptoSwapController.setAsset: Future<void>(SwapSide side, SwapAsset asset)
CryptoSwapController.setSlippage: void(SwapSlippage slippage)
CryptoSwapController.review: bool()
CryptoSwapController.cancelReview: void()
CryptoSwapController.confirm: Future<bool>()
CryptoSwapController.tick: void()
CryptoSwapController.dispose: void()
swapUnit: const int
SwapAsset: final class
SwapAsset.new: const new({required String id, required String name, required String symbol, required String glyph, required int balance, required int price, required int minimum, ImageProvider? image})
SwapAsset.id: get String
SwapAsset.name: get String
SwapAsset.symbol: get String
SwapAsset.glyph: get String
SwapAsset.balance: get int
SwapAsset.price: get int
SwapAsset.minimum: get int
SwapAsset.image: get ImageProvider?
SwapAsset.withImage: SwapAsset(ImageProvider? image)
SwapAsset.value: int(int units)
SwapAsset.operator ==: bool(Object other)
SwapAsset.hashCode: get int
SwapSide: enum {send, receive}
SwapSide.send: static final SwapSide
SwapSide.receive: static final SwapSide
SwapSlippage: enum {low, standard, high}
SwapSlippage.low: static final SwapSlippage
SwapSlippage.standard: static final SwapSlippage
SwapSlippage.high: static final SwapSlippage
SwapSlippage.new: const new(int basisPoints)
SwapSlippage.basisPoints: get int
SwapQuote: final class
SwapQuote.new: const new({required String from, required String to, required double rate, required DateTime quotedAt, required DateTime expiresAt, required int priceImpact, required int networkFee, required String feeSymbol})
SwapQuote.from: get String
SwapQuote.to: get String
SwapQuote.rate: get double
SwapQuote.quotedAt: get DateTime
SwapQuote.expiresAt: get DateTime
SwapQuote.priceImpact: get int
SwapQuote.networkFee: get int
SwapQuote.feeSymbol: get String
SwapQuote.validity: get Duration
SwapQuote.prices: bool(SwapAsset from, SwapAsset to)
SwapQuote.inverse: get SwapQuote
SwapQuote.receive: int(int units, SwapSlippage slippage)
SwapOrder: final class
SwapOrder.new: const new({required SwapAsset from, required SwapAsset to, required int amount, required int minimumReceived, required SwapQuote quote, required SwapSlippage slippage})
SwapOrder.from: get SwapAsset
SwapOrder.to: get SwapAsset
SwapOrder.amount: get int
SwapOrder.minimumReceived: get int
SwapOrder.quote: get SwapQuote
SwapOrder.slippage: get SwapSlippage
SwapReceipt: final class
SwapReceipt.new: const new({required String id, required SwapAsset from, required SwapAsset to, required int sent, required int received})
SwapReceipt.id: get String
SwapReceipt.from: get SwapAsset
SwapReceipt.to: get SwapAsset
SwapReceipt.sent: get int
SwapReceipt.received: get int
SwapNoNetwork: final class implements Exception
SwapNoNetwork.new: const new()
SwapQuoteExpired: final class implements Exception
SwapQuoteExpired.new: const new()
SwapService: abstract interface class
SwapService.new: new()
SwapService.assets: abstract Future<List<SwapAsset>>()
SwapService.quote: abstract Future<SwapQuote>(SwapAsset from, SwapAsset to)
SwapService.swap: abstract Future<SwapReceipt>(SwapOrder order)
SwapFormat: abstract interface class
SwapFormat.new: new()
SwapFormat.amount: abstract String(int units)
SwapFormat.asset: abstract String(int units, String symbol)
SwapFormat.money: abstract String(int cents)
SwapFormat.rate: abstract String(double rate)
SwapFormat.percent: abstract String(int basisPoints)
SwapFormat.input: abstract String(int units)
SwapFormat.parse: abstract int?(String text)
SampleSwapFormat: final class implements SwapFormat
SampleSwapFormat.new: const new(String language)
SampleSwapFormat.language: get String
SampleSwapFormat.amount: String(int units)
SampleSwapFormat.asset: String(int units, String symbol)
SampleSwapFormat.money: String(int cents)
SampleSwapFormat.rate: String(double rate)
SampleSwapFormat.percent: String(int basisPoints)
SampleSwapFormat.input: String(int units)
SampleSwapFormat.parse: int?(String text)
sampleSwapNow: get dynamic
sampleBitcoin: const dynamic
sampleEther: const dynamic
sampleTether: const dynamic
sampleSolana: const dynamic
sampleSwapAssets: const dynamic
sampleSwapLogo: ImageProvider(String symbol)
sampleSwapAssetsWith: List<SwapAsset>(ImageProvider Function(String symbol) logo)
sampleQuoteValidity: const dynamic
sampleSwapQuote: SwapQuote(SwapAsset from, SwapAsset to, DateTime at)
sampleSwapReady: SwapReady({int amount, Duration age, bool expired, bool reviewing, bool sending, SwapNotice? notice, List<SwapAsset> assets})
sampleSwapReceipt: get dynamic
SampleSwapService: final class implements SwapService
SampleSwapService.new: const new({List<SwapAsset> wallet, Duration latency, DateTime Function()? clock})
SampleSwapService.wallet: get List<SwapAsset>
SampleSwapService.latency: get Duration
SampleSwapService.clock: get DateTime Function()?
SampleSwapService.assets: Future<List<SwapAsset>>()
SampleSwapService.quote: Future<SwapQuote>(SwapAsset from, SwapAsset to)
SampleSwapService.swap: Future<SwapReceipt>(SwapOrder order)
cryptoSwapL10n: const Map<String, Map<String, String>>
CryptoSwapStrings: final class
CryptoSwapStrings.new: const new(String language)
CryptoSwapStrings.of: factory new(BuildContext context)
CryptoSwapStrings.language: get String
CryptoSwapStrings.title: get String
CryptoSwapStrings.back: get String
CryptoSwapStrings.loading: get String
CryptoSwapStrings.side: String(SwapSide side)
CryptoSwapStrings.atLeast: get String
CryptoSwapStrings.changeAsset: String(SwapSide side, String asset)
CryptoSwapStrings.receiveAsset: String(String symbol)
CryptoSwapStrings.half: get String
CryptoSwapStrings.max: get String
CryptoSwapStrings.amountOf: String(String symbol)
CryptoSwapStrings.balance: String(String amount)
CryptoSwapStrings.balanceValue: String(String amount, String value)
CryptoSwapStrings.direction: get String
CryptoSwapStrings.about: String(String value)
CryptoSwapStrings.problem: String(SwapProblem problem, {required String symbol, required String balance, required String minimum})
CryptoSwapStrings.refresh: get String
CryptoSwapStrings.review: get String
CryptoSwapStrings.details: get String
CryptoSwapStrings.rate: get String
CryptoSwapStrings.rateValue: String(String from, String rate, String to)
CryptoSwapStrings.refreshesIn: get String
CryptoSwapStrings.refreshing: get String
CryptoSwapStrings.priceImpact: get String
CryptoSwapStrings.networkFee: get String
CryptoSwapStrings.slippage: get String
CryptoSwapStrings.slippageSettings: get String
CryptoSwapStrings.slippageHint: get String
CryptoSwapStrings.done: get String
CryptoSwapStrings.confirmTitle: get String
CryptoSwapStrings.validFor: String(String time)
CryptoSwapStrings.cancel: get String
CryptoSwapStrings.close: get String
CryptoSwapStrings.sentTitle: get String
CryptoSwapStrings.sentMessage: String(String sent, String received)
CryptoSwapStrings.backToWallet: get String
CryptoSwapStrings.viewDetails: get String
CryptoSwapStrings.processing: get String
CryptoSwapStrings.processingLine: get String
CryptoSwapStrings.step: String(int step, int steps)
CryptoSwapStrings.notice: String(SwapNotice notice)
CryptoSwapStrings.emptyTitle: get String
CryptoSwapStrings.emptyMessage: get String
CryptoSwapStrings.addFunds: get String
CryptoSwapStrings.errorTitle: get String
CryptoSwapStrings.errorMessage: get String
CryptoSwapStrings.offlineTitle: get String
CryptoSwapStrings.offlineMessage: get String
CryptoSwapStrings.retry: get String
CryptoSwapStrings.countdown: String(Duration time)

Strings

Every label in English, French and Arabic. The screen follows the closest Localizations; the same table is in the manifest, ready for your ARB files.

Keyenfrar
swapTitleExchangeÉchangerتبادل
swapBackBackRetourرجوع
swapLoadingLoading your walletChargement de votre portefeuilleجارٍ تحميل محفظتك
swapYouSendYou sendVous envoyezترسل
swapYouReceiveYou receiveVous recevezتستلم
swapAtLeastYou receive at leastVous recevez au moinsتستلم على الأقل
swapChangeAsset{side}: {asset}. Change asset{side} : {asset}. Changer d’actif{side}: {asset}. تغيير الأصل
swapReceiveAssetYou receive {symbol}Vous recevez {symbol}تستلم {symbol}
swapHalfHalfMoitiéالنصف
swapMaxMaxMaxالكل
swapAmountOfAmount of {symbol} to sendMontant de {symbol} à envoyerمبلغ {symbol} المراد إرساله
swapBalanceBalance: {amount}Solde : {amount}الرصيد: {amount}
swapBalanceValueBalance: {amount} · ≈ {value}Solde : {amount} · ≈ {value}الرصيد: {amount} · ≈ {value}
swapDirectionSwap directionInverser le sensعكس الاتجاه
swapAbout≈ {value}≈ {value}≈ {value}
swapInsufficientNot enough {symbol}. You have {balance}.Pas assez de {symbol}. Vous avez {balance}.رصيد {symbol} غير كافٍ. لديك {balance}.
swapMinimumThe minimum exchange is {amount}.L’échange minimum est de {amount}.الحد الأدنى للتبادل هو {amount}.
swapExpiredThis quote has expired. Refresh to get a new rate.Ce cours a expiré. Actualisez pour en obtenir un nouveau.انتهت صلاحية هذا السعر. حدّث للحصول على سعر جديد.
swapRefreshRefreshActualiserتحديث
swapReviewReview exchangeVérifier l’échangeمراجعة التبادل
swapDetailsDetailsDétailsالتفاصيل
swapRateRateTauxالسعر
swapRateValue1 {from} = {rate} {to}1 {from} = {rate} {to}⁦1 {from} = {rate} {to}⁩
swapRefreshesInQuote refreshes inNouveau cours dansيتجدد السعر خلال
swapRefreshingRefreshingActualisationجارٍ التحديث
swapPriceImpactPrice impactImpact sur le prixتأثير السعر
swapNetworkFeeNetwork feeFrais de réseauرسوم الشبكة
swapSlippageSlippage toleranceTolérance de glissementهامش الانزلاق
swapSlippageSettingsSlippage settingsRéglage du glissementإعدادات الانزلاق
swapSlippageHintIf the price moves more than this before your exchange goes through, it’s cancelled and nothing leaves your wallet.Si le prix bouge davantage avant que l’échange aboutisse, il est annulé et rien ne quitte votre portefeuille.إذا تغيّر السعر أكثر من ذلك قبل إتمام التبادل، يُلغى ولا يخرج شيء من محفظتك.
swapDoneDoneTerminéتم
swapConfirmTitleConfirm exchangeConfirmer l’échangeتأكيد التبادل
swapValidForQuote valid for {time}Cours valable {time}السعر صالح لمدة {time}
swapCancelCancelAnnulerإلغاء
swapCloseCloseFermerإغلاق
swapSentTitleSwap sentÉchange envoyéتم إرسال التبادل
swapSentMessage{sent} for {received}. It usually lands in under a minute.{sent} contre {received}. Il arrive en général en moins d’une minute.{sent} مقابل {received}. يصل عادةً في أقل من دقيقة.
swapProcessingProcessingEn coursقيد المعالجة
swapProcessingLineWe’ll notify you when it landsNous vous préviendrons à son arrivéeسنُعلمك عند وصوله
swapStepStep {step} of {steps}Étape {step} sur {steps}الخطوة {step} من {steps}
swapBackToWalletBack to walletRetour au portefeuilleالعودة إلى المحفظة
swapViewDetailsView detailsVoir le détailعرض التفاصيل
swapFailedNoticeThe exchange didn’t go through. Nothing left your wallet.L’échange n’a pas abouti. Rien n’a quitté votre portefeuille.لم يتم التبادل. لم يخرج شيء من محفظتك.
swapOfflineNoticeYou’re offline. Nothing was sent.Vous êtes hors ligne. Rien n’a été envoyé.أنت غير متصل. لم يُرسَل شيء.
swapEmptyTitleNothing to exchange yetRien à échanger pour l’instantلا شيء للتبادل بعد
swapEmptyMessageAdd crypto to your wallet, then come back to exchange it.Ajoutez des cryptos à votre portefeuille, puis revenez les échanger.أضف عملات مشفّرة إلى محفظتك، ثم عُد لتبادلها.
swapAddFundsAdd cryptoAjouter des cryptosإضافة عملات مشفّرة
swapErrorTitleWe couldn’t load your walletImpossible de charger votre portefeuilleتعذّر تحميل محفظتك
swapErrorMessageOur server didn’t respond. Your funds are safe.Notre serveur n’a pas répondu. Vos fonds sont en sécurité.لم يستجب خادمنا. أموالك في أمان.
swapOfflineTitleYou are offlineVous êtes hors ligneأنت غير متصل
swapOfflineMessageCheck your connection, then try again.Vérifiez votre connexion, puis réessayez.تحقق من اتصالك ثم أعد المحاولة.
swapRetryTry againRéessayerأعد المحاولة

Reference renders

The 16 images published with version 0.2.0, rendered by the shared harness on macOS with Flutter 3.47.7. Each shows every state side by side, at the size of an iPhone (390 × 844) or of an Android phone (412 × 915). fliin verify crypto-swap compares your copy to them.

iOS · Light
Crypto swap, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Crypto swap, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Crypto swap, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Crypto swap, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Crypto swap, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Crypto swap, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Crypto swap, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Crypto swap, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Crypto swap, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Crypto swap, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Crypto swap, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Crypto swap, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Crypto swap, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Crypto swap, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Crypto swap, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Crypto swap, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • offer to exchange one crypto for another from a wallet
  • show what an amount of crypto buys at the live rate
  • show the rate, its countdown, the price impact and the network fee
  • pick the slippage tolerance of a swap
  • show that a swap exceeds the balance or falls under the minimum
  • confirm a swap in a sheet, then show that it was sent
Not for
  • a bank transfer between people: use a checkout (checkout)
  • a chart of an asset's price: use a chart (chart)
States
  • Ready
  • Insufficient
  • Minimum
  • Expired
  • Confirm
  • Sent
  • Loading
  • Empty
  • Error
  • Offline

Arrows to move, Enter to open, Escape to close.