flutter_connect 0.1.3 copy "flutter_connect: ^0.1.3" to clipboard
flutter_connect: ^0.1.3 copied to clipboard

Wallet-side Connect protocol with strict transport validation, explicit approval, and multi-chain signing adapters.

example/lib/main.dart

import 'dart:async';
import 'dart:typed_data';

import 'package:cryptography/cryptography.dart';
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:flutter_connect/flutter_connect.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MaterialApp(home: ConnectDemo()));
}

String hex(List<int> bytes) =>
    bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();

class DemoAccount implements WalletAccount {
  final SimpleKeyPair _key;
  @override
  final SigningSelection selection;
  DemoAccount(this._key, String address)
    : selection = SigningSelection(
        account: WalletIdentity(
          namespace: 'raw',
          reference: 'ed25519',
          address: address,
        ),
        profile: 'raw-ed25519',
        alg: coseEd25519,
      );
  @override
  Future<WalletSignature> sign(Uint8List canonicalPayload) async {
    final signature = await Ed25519().sign(canonicalPayload, keyPair: _key);
    return WalletSignature(
      hex(signature.bytes),
      publicKey: selection.account.address,
    );
  }
}

class DemoAccounts implements WalletAccountProvider {
  Future<WalletAccount>? _account;
  Future<WalletAccount> _create() async {
    final key = await Ed25519().newKeyPair();
    return DemoAccount(key, hex((await key.extractPublicKey()).bytes));
  }

  @override
  Future<List<WalletAccount>> getAccounts() async => [
    await (_account ??= _create()),
  ];
}

class ConnectDemo extends StatefulWidget {
  const ConnectDemo({super.key});
  @override
  State<ConnectDemo> createState() => _ConnectDemoState();
}

class _ConnectDemoState extends State<ConnectDemo> {
  final input = TextEditingController();
  ConnectBluetoothPeripheral? bluetooth;
  late final ConnectClient client;
  late final ConnectLinks links;
  StreamSubscription<ConnectRequest>? subscription;
  ConnectRequest? request;
  String? error;
  bool loading = false;
  @override
  void initState() {
    super.initState();
    client = ConnectClient(accountProvider: DemoAccounts());
    subscription = client.requests.listen((r) {
      if (request?.challenge.requestId != r.challenge.requestId) {
        unawaited(bluetooth?.stop());
        bluetooth = null;
      }
      if (mounted) {
        setState(() {
          request = r;
          error = null;
        });
      }
    });
    links = ConnectLinks(client: client, onError: showError);
    unawaited(links.start().catchError(showError));
  }

  void showError(Object e) {
    if (mounted) {
      setState(
        () => error = e is ConnectException
            ? e.code
            : 'Unable to process request',
      );
    }
  }

  Future<void> open() async {
    setState(() {
      loading = true;
      request = null;
      error = null;
    });
    try {
      await bluetooth?.stop();
      bluetooth = null;
      await client.handleUri(input.text);
    } catch (e) {
      showError(e);
    }
    if (mounted) setState(() => loading = false);
  }

  @override
  void dispose() {
    unawaited(links.dispose());
    unawaited(subscription?.cancel());
    unawaited(client.dispose());
    unawaited(bluetooth?.stop());
    input.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final r = request;
    return Scaffold(
      appBar: AppBar(title: const Text('Connect wallet')),
      body: ListView(
        padding: const EdgeInsets.all(24),
        children: [
          const Text(
            'Reference wallet',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 12),
          const Text(
            'Uses an ephemeral Ed25519 test account. Use the nearby dapp example, which allows raw-ed25519. A real wallet supplies its own account and signing implementation.',
          ),
          const SizedBox(height: 24),
          TextField(
            controller: input,
            decoration: const InputDecoration(
              labelText: 'Paste a Connect URI',
              border: OutlineInputBorder(),
            ),
          ),
          const SizedBox(height: 12),
          FilledButton(
            onPressed: loading ? null : open,
            child: Text(loading ? 'Loading…' : 'Review request'),
          ),
          if (error != null)
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16),
              child: Text(
                'Request error: $error',
                style: const TextStyle(color: Colors.red),
              ),
            ),
          if (r != null) ...[
            if (r.handoff != null &&
                r.state == ConnectState.awaitingUserApproval)
              OutlinedButton(
                onPressed: () async {
                  try {
                    await bluetooth?.stop();
                    final peripheral = ConnectBluetoothPeripheral(
                      handoff: r.handoff!,
                      onError: showError,
                    );
                    bluetooth = peripheral;
                    await peripheral.start();
                  } catch (e) {
                    showError(e);
                  }
                },
                child: const Text(
                  'Confirm website and enable nearby Bluetooth',
                ),
              ),
            if (r.response != null) ...[
              const Text(
                'Return this encrypted response to the portal. If Bluetooth is enabled, the portal can collect it now.',
              ),
              QrImageView(data: r.response!, size: 300),
              SelectableText(r.response!),
            ],
            const Divider(height: 40),
            const Text(
              'Sign in on another device?',
              style: TextStyle(fontSize: 22),
            ),
            const SizedBox(height: 12),
            Text(
              r.challenge.domain,
              style: const TextStyle(fontWeight: FontWeight.bold),
            ),
            Text(r.challenge.origin),
            Text('Expires: ${r.challenge.expiresAt}'),
            SelectableText(
              'Account: ${r.compatibleAccounts.first.selection.account.address}',
            ),
            Text('Profile: ${r.compatibleAccounts.first.selection.profile}'),
            Text('Status: ${r.state.name}'),
            if (r.state == ConnectState.awaitingUserApproval) ...[
              FilledButton(
                onPressed: () async {
                  try {
                    await r.approve(r.compatibleAccounts.first);
                    if (r.response != null) {
                      bluetooth?.publish(r.response!);
                    }
                  } catch (e) {
                    showError(e);
                  }
                },
                child: const Text('Approve sign-in'),
              ),
              OutlinedButton(onPressed: r.reject, child: const Text('Reject')),
            ],
          ],
        ],
      ),
    );
  }
}
0
likes
150
points
150
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Wallet-side Connect protocol with strict transport validation, explicit approval, and multi-chain signing adapters.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

app_links, bluetooth_low_energy, cryptography, flutter, flutter_wallet_validator, http

More

Packages that depend on flutter_connect