Flutter Cardano区块链交互插件ledger_cardano_plus的使用

Flutter Cardano区块链交互插件ledger_cardano_plus的使用

ledger-cardano-plus

一个用于Cardano区块链的Flutter Ledger应用插件
报告问题 · 请求功能 · Ledger Cardano Plus



概述

Ledger Nano设备是管理您的加密货币和NFT的理想硬件钱包。此Flutter插件通过ledger_flutter_plus包为Cardano区块链提供获取账户和签名交易的功能。

开始使用

安装

pubspec.yaml文件中安装最新版本的插件:

ledger_cardano_plus: ^latest-version
ledger_flutter_plus: ^latest-version

对于与ledger_flutter_plus包的集成,请参阅文档此处

设置和使用

首先,获取CardanoLedger实例并使用它来扫描和连接到设备。

final CardanoLedger cardanoLedgerConnector = CardanoLedger.ble(
    onPermissionRequest: (status) async {
      // 如果状态为不可用,则返回false
      // 使用permission_handler包
      Map<Permission, PermissionStatus> statuses = await [
        Permission.location,
        Permission.bluetoothScan,
        Permission.bluetoothConnect,
        Permission.bluetoothAdvertise,
      ].request();

      return statuses.values.where((status) => status.isDenied).isEmpty;
    },
  );

// 对于USB连接
// final CardanoLedger cardanoLedgerConnector = CardanoLedger.usb();

final devicesStream = cardanoLedgerConnector.scanForDevices();

// 最好监听流并允许用户选择所需的Ledger设备
final firstLedgerDevice = await devicesStream.first;

final cardanoApp = cardanoLedgerConnector.connect(firstLedgerDevice);

// 获取接收地址
final receiveAddress = await cardanoApp.deriveReceiveAddress(
      addressIndex: addressIndex,
      network: CardanoNetwork.mainnet(),
    );

要获取更深入的示例,包括设备选择对话框,请查看此存储库中的示例项目

赞助商

我们的顶级赞助商如下所示!

Project Catalyst

贡献

贡献使开源社区成为学习、启发和创造的绝佳场所。任何贡献都将受到极大的赞赏。

如果您有任何改进建议,请在仓库上创建一个拉取请求。您也可以简单地打开一个带有标签enhancement的issue。

  1. 分支项目
  2. 创建您的功能分支 (git checkout -b feature/my-feature)
  3. 提交更改 (git commit -m 'feat: my new feature')
  4. 推送到分支 (git push origin feature/my-feature)
  5. 打开拉取请求

请阅读我们的贡献指南,并尝试遵循常规提交规范

运行集成测试

要运行ledger-cardano-plus SDK的集成测试,请按照以下步骤操作:

  1. 设置环境: 确保您的机器上已安装Flutter。您可以从Flutter官方站点下载它。

  2. 克隆仓库: 如果您尚未克隆,从GitHub克隆ledger-cardano-plus仓库:

    git clone https://github.com/vespr-wallet/ledger-cardano-plus.git
    cd ledger-cardano-plus
    
  3. 导航到集成测试目录: 更改到包含集成测试的目录:

    cd example/integration_test
    
  4. 运行测试: 使用以下Flutter命令执行所有集成测试:

    cd example
    flutter test integration_test/*_tests.dart
    

    若要运行特定的集成测试文件,请提供测试文件路径:

    cd example
    flutter test integration_test/cardano_ledger_serial_version_tests.dart
    

    若要运行测试文件中的特定测试用例,请使用-n标志后跟测试名称:

    cd example
    flutter test integration_test/cardano_ledger_serial_version_tests.dart -n "Should correctly get the serial number of the device"
    

    确保您的开发环境能够与Ledger设备通信,并且在运行测试之前设备已连接并解锁。

许可证

ledger_cardano_plus SDK在MIT许可证下发布。详情见LICENSE文件。


示例代码

以下是main.dart文件的示例代码:

import 'dart:async';

import 'package:example/sample_utils/operations.dart';
import 'package:example/widgets/available_devices.dart';
import 'package:flutter/material.dart';
import 'package:ledger_cardano_plus/ledger_cardano_plus.dart';
import 'package:permission_handler/permission_handler.dart';

const _awaitingLedgerResponse = '[Awaiting Ledger Response...]';

void main() {
  CardanoLedger.debugPrintEnabled = true;
  runApp(const MainWidget());
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: SizedBox(
            width: double.infinity,
            child: MyApp(),
          ),
        ),
      ),
    );
  }
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late final CardanoLedger cardanoLedgerConnector = CardanoLedger.ble(
    onPermissionRequest: ({required bool unsupported}) async {
      if (unsupported) {
        return false;
      }

      Map<Permission, PermissionStatus> statuses = await [
        Permission.location,
        Permission.bluetoothScan,
        Permission.bluetoothConnect,
        Permission.bluetoothAdvertise,
      ].request();

      return statuses.values.where((status) => status.isDenied).isEmpty;
    },
  );
  bool connecting = false;
  CardanoLedgerConnection? cardanoLedgerConnection;

  String? error;
  String? resultTitle;
  String? resultData;

  void _onOperationRequested({
    required String operation,
    required FutureOr<String> Function() invoker,
  }) async {
    try {
      resultTitle = operation;
      resultData = _awaitingLedgerResponse;

      setState(() {});

      resultData = await invoker();
    } catch (e) {
      resultData = 'Unhandled Error: ${e.toString()}';
    }
    setState(() {});
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    final cardanoLedgerConnection = this.cardanoLedgerConnection;
    final error = this.error;

    if (connecting) {
      return const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              'Connecting to Ledger Device...',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 32),
            CircularProgressIndicator(),
          ],
        ),
      );
    }

    if (cardanoLedgerConnection == null || error != null) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ElevatedButton(
              onPressed: _onScanForLedgerDevicesPressed,
              child: const Text('Scan for Ledger Devices'),
            ),
            if (error != null)
              Text(
                'Error connecting to selected device\n$error',
                textAlign: TextAlign.center,
                style: const TextStyle(
                  fontSize: 14,
                  color: Colors.redAccent,
                  fontWeight: FontWeight.bold,
                ),
              )
          ],
        ),
      );
    }

    final selectedDevice = cardanoLedgerConnection.device;

    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text(
          'Connected to ${selectedDevice.name}\n${selectedDevice.id}',
          textAlign: TextAlign.center,
          style: const TextStyle(
            fontSize: 14,
            color: Colors.blueAccent,
            fontWeight: FontWeight.bold,
          ),
        ),
        const Divider(),
        const SizedBox(height: 8),
        OutlinedButton(
          onPressed: () => _onOperationRequested(
            operation: "Disconnect Device",
            invoker: () async {
              final result = reset(cardanoLedgerConnection);
              await cardanoLedgerConnection.disconnect();
              setState(() {
                this.cardanoLedgerConnection = null;
                this.error = null;
                connecting = false;
              });

              return result;
            },
          ),
          child: const Text('Disconnect Device'),
        ),
        const SizedBox(height: 16),
        Wrap(
          spacing: 16,
          runSpacing: 4,
          alignment: WrapAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => _onOperationRequested(
                operation: "Fetch Serial Number",
                invoker: () => fetchSerial(cardanoLedgerConnection),
              ),
              child: const Text('Fetch Serial Number'),
            ),
            ElevatedButton(
              onPressed: () => _onOperationRequested(
                operation: "Fetch App Version",
                invoker: () => fetchVersion(cardanoLedgerConnection),
              ),
              child: const Text('Fetch App Version'),
            ),
            ElevatedButton(
              onPressed: () => _onOperationRequested(
                operation: "Fetch Cardano Wallet Public Key",
                invoker: () => fetchPublicKey(cardanoLedgerConnection),
              ),
              child: const Text('Fetch Public Key'),
            ),
            ElevatedButton(
              onPressed: () => _onOperationRequested(
                operation: "Fetch Stake Address",
                invoker: () => fetchStakeAddress(cardanoLedgerConnection),
              ),
              child: const Text('Fetch Stake Address'),
            ),
            ElevatedButton(
              onPressed: () => _onOperationRequested(
                operation: "Fetch Receive Addresses",
                invoker: () => fetchReceiveAddresses(
                  cardanoLedgerConnection,
                  addressIndices: [0, 1, 2, 3],
                ),
              ),
              child: const Text('Fetch Receive Addresses'),
            ),
            ElevatedButton(
              onPressed: () => _onOperationRequested(
                operation: "Fetch Change Addresses",
                invoker: () => fetchChangeAddresses(
                  cardanoLedgerConnection,
                  addressIndices: [0, 1, 2, 3],
                ),
              ),
              child: const Text('Fetch Change Addresses'),
            ),
            ElevatedButton(
              onPressed: () => _onOperationRequested(
                operation: "Sign Transaction",
                invoker: () => signTransaction(cardanoLedgerConnection),
              ),
              child: const Text('Sign Transaction'),
            ),
          ],
        ),
        const SizedBox(height: 16),
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Container(
              padding: const EdgeInsets.all(8.0),
              constraints: const BoxConstraints(
                minWidth: double.infinity,
              ),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey),
                borderRadius: BorderRadius.circular(8.0),
              ),
              child: AnimatedSize(
                duration: const Duration(milliseconds: 300),
                alignment: Alignment.topCenter,
                child: SingleChildScrollView(
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(resultTitle ?? "[Operation]",
                          style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                      const Divider(),
                      const SizedBox(height: 4),
                      Text(resultData ?? "[Ledger Result]"),
                      if (resultData == _awaitingLedgerResponse) ...[
                        const SizedBox(height: 16),
                        const CircularProgressIndicator(),
                      ]
                    ],
                  ),
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }

  void _onScanForLedgerDevicesPressed() async {
    final selectedLedgerDevice = await showAdaptiveDialog<LedgerDevice>(
      context: context,
      barrierDismissible: true,
      builder: (context) => AlertDialog(content: AvailableDevices(ledger: cardanoLedgerConnector)),
    );
    if (selectedLedgerDevice != null) {
      setState(() => connecting = true);
      try {
        final establishedConnection = await cardanoLedgerConnector.connect(selectedLedgerDevice);
        // ignore: use_build_context_synchronously
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text("Connected"),
            duration: Duration(seconds: 1),
          ),
        );
        setState(() {
          connecting = false;
          error = null;
          cardanoLedgerConnection = establishedConnection;
        });
      } catch (e) {
        setState(() {
          connecting = false;
          error = e.toString();
          cardanoLedgerConnection = null;
        });
        // ignore: use_build_context_synchronously
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text("Failed to connect"),
            duration: Duration(seconds: 1),
          ),
        );
      }
    }
  }
}

更多关于Flutter Cardano区块链交互插件ledger_cardano_plus的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter Cardano区块链交互插件ledger_cardano_plus的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用 ledger_cardano_plus 插件与 Cardano 区块链进行交互的示例代码。这个插件通常用于与 Ledger 硬件钱包进行交互,执行签名交易等操作。以下示例将展示如何连接到 Ledger 设备、获取公钥以及签名交易。

首先,确保你的 Flutter 项目中已经添加了 ledger_cardano_plus 依赖。在 pubspec.yaml 文件中添加以下内容:

dependencies:
  flutter:
    sdk: flutter
  ledger_cardano_plus: ^最新版本号

然后运行 flutter pub get 来获取依赖。

接下来,是示例代码,展示了如何使用 ledger_cardano_plus 插件:

import 'package:flutter/material.dart';
import 'package:ledger_cardano_plus/ledger_cardano_plus.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String publicKey = '';
  String signedTxHex = '';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Ledger Cardano Plus Example'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Public Key:'),
              Text(publicKey, style: TextStyle(fontSize: 16)),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: _getPublicKey,
                child: Text('Get Public Key'),
              ),
              SizedBox(height: 20),
              Text('Signed Transaction Hex:'),
              Text(signedTxHex, style: TextStyle(fontSize: 16)),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: _signTransaction,
                child: Text('Sign Transaction'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _getPublicKey() async {
    try {
      // Initialize the Cardano app on the Ledger device
      final app = await CardanoApp.connect();

      // Get the public key for the first account (index 0, bip44 path m/1852'/1815'/0'/0/0)
      final publicKey = await app.getPublicKey(
        path: DerivationPath.fromBIP44(coinType: 1815, account: 0, change: 0, addressIndex: 0),
      );

      setState(() {
        this.publicKey = publicKey.toHex();
      });
    } catch (e) {
      print('Error getting public key: $e');
    }
  }

  Future<void> _signTransaction() async {
    try {
      // Initialize the Cardano app on the Ledger device
      final app = await CardanoApp.connect();

      // Example transaction data (this should be constructed according to your needs)
      final txData = TransactionData(
        // Populate with actual transaction data
        // For example:
        // inputs: [TransactionInput(...)],
        // outputs: [TransactionOutput(...)],
        // fee: ...,
        // ttl: ...,
        // certificate: ...,
        // withdrawals: ...,
        // metadata: ...,
        // validFrom: ...,
        // mint: ...,
      );

      // Sign the transaction
      final signedTx = await app.signTransaction(
        path: DerivationPath.fromBIP44(coinType: 1815, account: 0, change: 0, addressIndex: 0),
        transactionData: txData,
      );

      setState(() {
        this.signedTxHex = signedTx.toHex();
      });
    } catch (e) {
      print('Error signing transaction: $e');
    }
  }
}

// Define TransactionData and related classes as needed based on your transaction format
// These classes are placeholders and should be implemented according to the Cardano transaction format
class TransactionData {
  // Add fields and methods to construct and serialize the transaction data
}

注意

  1. TransactionData 类和相关字段需要根据你的实际需求来定义和填充。这通常涉及到对 Cardano 交易结构的深入了解,包括输入、输出、费用等。
  2. 示例代码中的 DerivationPath.fromBIP44 方法用于指定从 Ledger 设备中派生的密钥路径。根据你的需求调整 coinType, account, change, 和 addressIndex
  3. 在实际使用中,确保正确处理错误和异常情况,特别是与硬件设备的交互可能会因为各种原因失败。

由于 ledger_cardano_plus 插件的具体实现和 API 可能会随着版本更新而变化,建议查阅最新的官方文档和示例代码来获取最准确的信息。

回到顶部