Flutter账本管理插件ledger_flutter的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter账本管理插件ledger_flutter的使用

ledger-flutter

一个Flutter插件,用于通过USB和BLE扫描、连接和签名Ledger Nano设备上的交易。

概述

Ledger Nano设备是管理您的加密货币和NFT的最佳硬件钱包。这个Flutter插件使您能够轻松找到附近的Ledger设备,与它们连接并通过USB和/或BLE签名交易。

Web3生态系统集成

我们正在扩展Flutter生态系统以发展Web3社区。请查看下面的其他Web3包:

  • WalletConnect
  • Algorand

支持的设备

BLE USB
Android ✔️ ✔️
iOS ✔️

开始使用

安装

通过pub.dev安装最新版本的此包:

ledger_flutter: ^latest-version

例如,添加Algorand支持:

ledger_algorand: ^latest-version

设置

创建一个新的LedgerOptions实例并将其传递给Ledger构造函数。

final options = LedgerOptions(
  maxScanDuration: const Duration(milliseconds: 5000),
);

final ledger = Ledger(
  options: options,
);

Android

该包使用以下权限:

  • ACCESS_FINE_LOCATION:此权限是因为旧的Nexus设备需要位置服务才能提供可靠的扫描结果。
  • BLUETOOTH:允许应用程序连接到配对的蓝牙设备。
  • BLUETOOTH_ADMIN:允许应用程序发现和配对蓝牙设备。

AndroidManifest.xml中添加以下权限:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<!--bibo01 : hardware option-->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>

<!-- required for API 18 - 30 -->
<uses-permission
    android:name="android.permission.BLUETOOTH"
    android:maxSdkVersion="30" />
<uses-permission
    android:name="android.permission.BLUETOOTH_ADMIN"
    android:maxSdkVersion="30" />

<!-- API 31+ -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission
    android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

iOS

对于iOS,必须在应用的Info.plist文件中添加以下条目。如果不这样做,则无法访问Core Bluetooth。

iOS13及以上

<key>NSBluetoothAlwaysUsageDescription</key>
<string>此应用程序使用蓝牙来查找、连接并与您的Ledger Nano X签署交易。</string>

iOS12及以下

<key>NSBluetoothPeripheralUsageDescription</key>
<string>此应用程序使用蓝牙来查找、连接并与您的Ledger Nano X签署交易。</string>

Ledger App插件

每个区块链都有自己的协议,需要实现后才能获取公钥和签名交易。我们引入了Ledger App插件的概念,任何开发人员都可以轻松创建和集成自己的Ledger App插件,并与社区共享。

我们为Algorand区块链添加了第一个支持:

ledger_algorand: ^latest-version
final algorandApp = AlgorandLedgerApp(ledger);
final publicKeys = await algorandApp.getAccounts(device);

现有插件:

  • Algorand
  • 创建我自己的插件

使用方法

扫描附近设备

您可以使用scan()方法扫描附近的Ledger设备。这会返回一个可以监听的新设备被发现时发出的Stream

final subscription = ledger.scan().listen((device) => print(device));

扫描会在maxScanDuration过去或调用stop()方法时停止。maxScanDuration是BLE发现运行以查找附近设备的最大时间。

await ledger.stop();

权限

Ledger Flutter插件使用Bluetooth Low Energy,这需要在iOS和Android上处理某些权限。插件每次需要权限时都会发送回调。只需覆盖onPermissionRequest并让permission_handler包处理其余部分即可。

final ledger = Ledger(
  options: options,
  onPermissionRequest: (status) async {
    Map<Permission, PermissionStatus> statuses = await [
      Permission.location,
      Permission.bluetoothScan,
      Permission.bluetoothConnect,
      Permission.bluetoothAdvertise,
    ].request();

    if (status != BleStatus.ready) {
      return false;
    }

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

连接到Ledger设备

一旦找到LedgerDevice,可以使用connect()方法轻松连接到设备。

await ledger.connect(device);

如果无法连接到设备,则会抛出LedgerException

该包还包括一个devices流,当连接状态更改时更新。

final subscription = ledger.devices.listen((state) => print(state));

获取公钥

根据所需的区块链和Ledger Application插件,可以使用getAccounts()方法从Ledger Nano设备获取公钥。

final algorandApp = AlgorandLedgerApp(ledger);

final publicKeys = await algorandApp.getAccounts(device);
accounts.addAll(publicKeys.map((pk) => Address.fromAlgorandAddress(pk)).toList());

签名交易

您可以使用提供的LedgerApp轻松签名交易。

这是一个使用algorand_dart SDK的示例:

final algorandApp = AlgorandLedgerApp(channel.ledger);
final signature = await algorandApp.signTransaction(
    device,
    transaction.toBytes(),
);

final signedTx = SignedTransaction(
  transaction: event.transaction,
  signature: signature,
);

final txId = await algorand.sendTransaction(
    signedTx,
    waitForConfirmation: true,
);

断开连接

使用disconnect()方法关闭与Ledger设备的已建立连接。

await ledger.disconnect(device);

处理

始终使用close()方法关闭所有连接并释放潜在的监听器资源,以免泄漏任何资源。

await ledger.close();

LedgerException

每个方法都可能抛出LedgerException,其中包含消息、原因和潜在错误代码。

try {
  await channel.ledger.connect(device);
} on LedgerException catch (ex) {
  await channel.ledger.disconnect(device);
}

自定义Ledger App插件

每个区块链都有自己的APDU协议,在能够获取公钥和签名交易之前需要实现。

如果您想支持另一个区块链(如Ethereum),请按照以下步骤操作。您可以在ledger_algorand中检查实现细节。

1. 创建新的LedgerApp

创建一个新类(例如EthereumLedgerApp)并扩展自LedgerApp

class EthereumLedgerApp extends LedgerApp {
  EthereumLedgerApp(super.ledger);

  [@override](/user/override)
  Future<List<String>> getAccounts(LedgerDevice device) async {
    throw UnimplementedError();
  }

  [@override](/user/override)
  Future<Uint8List> signTransaction(
    LedgerDevice device,
    Uint8List transaction,
  ) {
    throw UnimplementedError();
  }

  [@override](/user/override)
  Future<List<Uint8List>> signTransactions(
    LedgerDevice device,
    List<Uint8List> transactions,
  ) async {
    throw UnimplementedError();
  }
}

2. 定义Ledger操作

为每个APDU命令创建一个新的操作类(例如EthereumPublicKeyOperation)并扩展自LedgerOperation。遵循并实现所需区块链的APDU协议。

class AlgorandPublicKeyOperation extends LedgerOperation<List<String>> {
  final int accountIndex;

  AlgorandPublicKeyOperation({
    this.accountIndex = 0,
  });

  [@override](/user/override)
  Future<Uint8List> write(ByteDataWriter writer, int index, int mtu) async {
    writer.writeUint8(0x80); // ALGORAND_CLA
    writer.writeUint8(0x03); // PUBLIC_KEY_INS
    writer.writeUint8(0x00); // P1_FIRST
    writer.writeUint8(0x00); // P2_LAST
    writer.writeUint8(0x04); // ACCOUNT_INDEX_DATA_SIZE

    writer.writeUint32(accountIndex); // Account index as bytearray

    return writer.toBytes();
  }

  [@override](/user/override)
  Future<List<String>> read(ByteDataReader reader, int index, int mtu) async {
    return [
      Address(publicKey: reader.read(reader.remainingLength)).encodedAddress,
    ];
  }
}

3. 实现LedgerApp

最后一步是使用Ledger客户端在连接的Ledger上执行所需的操作。在LedgerApp上实现所需的方法。

注意,不同区块链的LedgerApp接口可能会改变,因此请随时打开拉取请求。

[@override](/user/override)
Future<List<String>> getAccounts(LedgerDevice device) async {
    return ledger.sendOperation<List<String>>(
      device,
      AlgorandPublicKeyOperation(accountIndex: accountIndex),
    );
}

赞助商

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

Defly

Blockshake

贡献

贡献是开源社区如此令人惊叹的学习、启发和创造的地方的原因。您所做的任何贡献都非常感谢。

如果您有一个能使此项目更好的建议,请fork仓库并创建一个pull请求。您也可以简单地打开一个带有标签enhancement的问题。

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/my-feature)
  3. Commit your Changes (git commit -m 'feat: my new feature')
  4. Push to the Branch (git push origin feature/my-feature)
  5. Open a Pull Request

请阅读我们的贡献指南并尝试遵循Conventional Commits。

许可证

ledger_flutter SDK发布在MIT许可证下。详情请参见LICENSE。

示例Demo

以下是完整的示例demo,展示了如何使用ledger_flutter插件进行设备扫描、连接和签名交易:

import 'package:flutter/material.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:ledger_algorand/ledger_algorand.dart';

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

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final options = LedgerOptions(
    maxScanDuration: const Duration(seconds: 10),
  );

  final ledger = Ledger(options: options);
  final algorandApp = AlgorandLedgerApp(ledger);

  [@override](/user/override)
  void initState() {
    super.initState();
    scanDevices();
  }

  void scanDevices() {
    final subscription = ledger.scan().listen((device) {
      print('Found device: $device');
      connectToDevice(device);
    });
  }

  void connectToDevice(LedgerDevice device) async {
    try {
      await ledger.connect(device);
      print('Connected to device: $device');
      getPublicKeys(device);
    } catch (e) {
      print('Failed to connect: $e');
    }
  }

  void getPublicKeys(LedgerDevice device) async {
    final publicKeys = await algorandApp.getAccounts(device);
    print('Public Keys: $publicKeys');
    // Disconnect after getting keys
    await ledger.disconnect(device);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Ledger Flutter Demo'),
        ),
        body: Center(
          child: Text('Check console for device information.'),
        ),
      ),
    );
  }

  [@override](/user/override)
  void dispose() {
    ledger.close();
    super.dispose();
  }
}

更多关于Flutter账本管理插件ledger_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter账本管理插件ledger_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,关于如何在Flutter项目中使用ledger_flutter插件来进行账本管理,下面是一个基本的代码示例。这个示例展示了如何安装插件、初始化账本、进行简单的读写操作。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加ledger_flutter依赖:

dependencies:
  flutter:
    sdk: flutter
  ledger_flutter: ^最新版本号 # 请替换为实际可用的最新版本号

然后运行flutter pub get来安装依赖。

2. 初始化账本

在Flutter项目的main.dart文件中,你需要导入ledger_flutter插件并进行初始化。以下是一个基本的初始化示例:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ledger Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LedgerDemoPage(),
    );
  }
}

class LedgerDemoPage extends StatefulWidget {
  @override
  _LedgerDemoPageState createState() => _LedgerDemoPageState();
}

class _LedgerDemoPageState extends State<LedgerDemoPage> {
  late Ledger ledger;

  @override
  void initState() {
    super.initState();
    // 初始化账本,这里假设你已经有了一个Ledger实例
    // 在实际应用中,你可能需要根据具体的硬件或软件账本进行初始化
    ledger = Ledger(); // 这是一个假设的初始化方式,实际使用时请参考ledger_flutter的文档
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Ledger Flutter Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () async {
                // 写入数据到账本
                String result = await ledger.writeData('exampleData');
                print('Write Result: $result');
              },
              child: Text('Write Data'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                // 从账本读取数据
                String data = await ledger.readData();
                print('Read Data: $data');
              },
              child: Text('Read Data'),
            ),
          ],
        ),
      ),
    );
  }
}

// 假设的Ledger类定义(实际使用时请参考ledger_flutter提供的API)
class Ledger {
  Future<String> writeData(String data) async {
    // 模拟写入数据操作
    // 在实际应用中,这里会调用ledger_flutter提供的API进行账本写入
    return 'Data written successfully: $data';
  }

  Future<String> readData() async {
    // 模拟读取数据操作
    // 在实际应用中,这里会调用ledger_flutter提供的API进行账本读取
    return 'Example Data Read';
  }
}

注意事项

  1. 实际初始化:上面的代码中Ledger类的初始化方法是假设的。在实际使用中,你需要根据ledger_flutter插件提供的API和文档来正确初始化账本。这可能涉及到与硬件设备的连接、配置等。

  2. 异步操作:账本的读写操作通常是异步的,因此在Flutter中你需要使用asyncawait关键字来处理这些操作。

  3. 错误处理:在实际应用中,你需要添加适当的错误处理逻辑,以处理可能出现的异常情况,比如连接失败、数据读写错误等。

  4. 插件文档:请参考ledger_flutter插件的官方文档,以获取更详细的使用指南和API参考。

由于ledger_flutter插件的具体API和实现细节可能随着版本更新而变化,因此建议直接查阅最新的官方文档以获取准确的信息。

回到顶部