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.
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-swapAlso 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| Key | en | fr | ar |
|---|---|---|---|
swapTitle | Exchange | Échanger | تبادل |
swapBack | Back | Retour | رجوع |
swapLoading | Loading your wallet | Chargement de votre portefeuille | جارٍ تحميل محفظتك |
swapYouSend | You send | Vous envoyez | ترسل |
swapYouReceive | You receive | Vous recevez | تستلم |
swapAtLeast | You receive at least | Vous recevez au moins | تستلم على الأقل |
swapChangeAsset | {side}: {asset}. Change asset | {side} : {asset}. Changer d’actif | {side}: {asset}. تغيير الأصل |
swapReceiveAsset | You receive {symbol} | Vous recevez {symbol} | تستلم {symbol} |
swapHalf | Half | Moitié | النصف |
swapMax | Max | Max | الكل |
swapAmountOf | Amount of {symbol} to send | Montant de {symbol} à envoyer | مبلغ {symbol} المراد إرساله |
swapBalance | Balance: {amount} | Solde : {amount} | الرصيد: {amount} |
swapBalanceValue | Balance: {amount} · ≈ {value} | Solde : {amount} · ≈ {value} | الرصيد: {amount} · ≈ {value} |
swapDirection | Swap direction | Inverser le sens | عكس الاتجاه |
swapAbout | ≈ {value} | ≈ {value} | ≈ {value} |
swapInsufficient | Not enough {symbol}. You have {balance}. | Pas assez de {symbol}. Vous avez {balance}. | رصيد {symbol} غير كافٍ. لديك {balance}. |
swapMinimum | The minimum exchange is {amount}. | L’échange minimum est de {amount}. | الحد الأدنى للتبادل هو {amount}. |
swapExpired | This quote has expired. Refresh to get a new rate. | Ce cours a expiré. Actualisez pour en obtenir un nouveau. | انتهت صلاحية هذا السعر. حدّث للحصول على سعر جديد. |
swapRefresh | Refresh | Actualiser | تحديث |
swapReview | Review exchange | Vérifier l’échange | مراجعة التبادل |
swapDetails | Details | Détails | التفاصيل |
swapRate | Rate | Taux | السعر |
swapRateValue | 1 {from} = {rate} {to} | 1 {from} = {rate} {to} | 1 {from} = {rate} {to} |
swapRefreshesIn | Quote refreshes in | Nouveau cours dans | يتجدد السعر خلال |
swapRefreshing | Refreshing | Actualisation | جارٍ التحديث |
swapPriceImpact | Price impact | Impact sur le prix | تأثير السعر |
swapNetworkFee | Network fee | Frais de réseau | رسوم الشبكة |
swapSlippage | Slippage tolerance | Tolérance de glissement | هامش الانزلاق |
swapSlippageSettings | Slippage settings | Réglage du glissement | إعدادات الانزلاق |
swapSlippageHint | If 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. | إذا تغيّر السعر أكثر من ذلك قبل إتمام التبادل، يُلغى ولا يخرج شيء من محفظتك. |
swapDone | Done | Terminé | تم |
swapConfirmTitle | Confirm exchange | Confirmer l’échange | تأكيد التبادل |
swapValidFor | Quote valid for {time} | Cours valable {time} | السعر صالح لمدة {time} |
swapCancel | Cancel | Annuler | إلغاء |
swapClose | Close | Fermer | إغلاق |
swapSentTitle | Swap 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}. يصل عادةً في أقل من دقيقة. |
swapProcessing | Processing | En cours | قيد المعالجة |
swapProcessingLine | We’ll notify you when it lands | Nous vous préviendrons à son arrivée | سنُعلمك عند وصوله |
swapStep | Step {step} of {steps} | Étape {step} sur {steps} | الخطوة {step} من {steps} |
swapBackToWallet | Back to wallet | Retour au portefeuille | العودة إلى المحفظة |
swapViewDetails | View details | Voir le détail | عرض التفاصيل |
swapFailedNotice | The exchange didn’t go through. Nothing left your wallet. | L’échange n’a pas abouti. Rien n’a quitté votre portefeuille. | لم يتم التبادل. لم يخرج شيء من محفظتك. |
swapOfflineNotice | You’re offline. Nothing was sent. | Vous êtes hors ligne. Rien n’a été envoyé. | أنت غير متصل. لم يُرسَل شيء. |
swapEmptyTitle | Nothing to exchange yet | Rien à échanger pour l’instant | لا شيء للتبادل بعد |
swapEmptyMessage | Add crypto to your wallet, then come back to exchange it. | Ajoutez des cryptos à votre portefeuille, puis revenez les échanger. | أضف عملات مشفّرة إلى محفظتك، ثم عُد لتبادلها. |
swapAddFunds | Add crypto | Ajouter des cryptos | إضافة عملات مشفّرة |
swapErrorTitle | We couldn’t load your wallet | Impossible de charger votre portefeuille | تعذّر تحميل محفظتك |
swapErrorMessage | Our server didn’t respond. Your funds are safe. | Notre serveur n’a pas répondu. Vos fonds sont en sécurité. | لم يستجب خادمنا. أموالك في أمان. |
swapOfflineTitle | You are offline | Vous êtes hors ligne | أنت غير متصل |
swapOfflineMessage | Check your connection, then try again. | Vérifiez votre connexion, puis réessayez. | تحقق من اتصالك ثم أعد المحاولة. |
swapRetry | Try again | Ré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.
















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