RootSoft / walletconnect-dart-sdk

Open protocol for connecting dApps to mobile wallets with QR code scanning or deep linking.
MIT License
104 stars 61 forks source link

FormatException thrown when signing the transaction with AlgorandWalletConnectProvider #100

Open jasonhtpham opened 1 year ago

jasonhtpham commented 1 year ago

After connecting to my account on Pera Wallet, I faced this error

Error: FormatException: Invalid character (at character 77)
I/flutter (10855): gqNzaWfEQIwtNFWFUDYnI49KtgVk5nGLCiEP9M/F7p4Zz2acj2FNEdZT3Dq56o+hKaSTYTzrU229

When I tried to sign the transaction in this function:

@override
  Future<String?> optInApp({
    required int appId,
  }) async {
    logger.i("Opting in to app: $appId");

    if (await optedIn(appId)) {
      logger.i("Already opted in to app: $appId");
      return null;
    }

    final sender =
        Address.fromAlgorandAddress(_connector.connector.session.accounts[0]);

    // Fetch the suggested transaction params
    final params = await _algorand.getSuggestedTransactionParams();

    // Build the transaction
    final transaction = await (ApplicationOptInTransactionBuilder()
          ..sender = sender
          ..noteText = 'Opt in to app $appId from $sender'
          ..applicationId = appId
          ..suggestedParams = params)
        .build();

    logger.i("Tx created: $transaction");

    try {
      // Sign the transaction
      final txBytes = Encoder.encodeMessagePack(transaction.toMessagePack());
      logger.i("Tx bytes: $txBytes");

      final signedBytes = await _provider.signTransaction(
        txBytes,
        params: {
          'message': 'Opt in to app $appId from $sender',
        },
      );

      logger.i("Tx signed: $signedBytes");

      // Broadcast the transaction
      final txId = await _algorand.sendRawTransactions(
        signedBytes,
        waitForConfirmation: true,
      );
      return txId;
    } catch (e) {
      final err = _tryCast(e, fallback: AlgorandException);
      if (err is AlgorandException) {
        AlgorandException error = err as AlgorandException;
        debugPrint('Error: ${error.message}');
      } else {
        debugPrint('Error: $e');
      }
      return null;
    }
  }

I tried to debug it and found that it happened when I tried to sign the transaction

      final txBytes = Encoder.encodeMessagePack(transaction.toMessagePack());
      final signedBytes = await _provider.signTransaction(
        txBytes,
        params: {
          'message': 'Opt in to app $appId from $sender',
        },
      );

My initialization code and the connect method in my AlgorandConnector class:

AlgorandConnector() {
    _connector = WalletConnectQrCodeModal(
      connector: WalletConnect(
        bridge: 'https://bridge.walletconnect.org',
        clientMeta: const PeerMeta(
          // <-- Meta data of your app appearing in the wallet when connecting
          name: 'Shark Tank DApp',
          description: 'Shark Tank DApp',
          url: 'https://walletconnect.org',
          icons: [
            'https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media'
          ],
        ),
      ),
    );

    _provider = AlgorandWalletConnectProvider(_connector.connector);
  }

  T? _tryCast<T>(dynamic value, {T? fallback}) {
    try {
      return (value as T);
    } on TypeError catch (_) {
      return fallback;
    }
  }

  @override
  Future<SessionStatus?> connect(BuildContext context) async {
    return await _connector.connect(context, chainId: 4160);
  }

It used to work perfectly fine when I tested it a few days ago. I only faced this error yesterday. It also happened when I tested on a different device. Besides, if I connect to the Dapp using the QR Code, everything works perfectly fine. But I am working on a mobile application so I need the Deep Linking method to work. I had a discussion with @3ph here and was suggested to discuss with @RootSoft

This is the screenshot of the error image

RootSoft commented 1 year ago

@jasonhtpham What WalletConnect & Algorand Dart version are you currently on?

jasonhtpham commented 1 year ago

Hi, I am on:

walletconnect_dart: ^0.0.11 algorand_dart: ^2.0.0-dev.8

I am also using

walletconnect_qrcode_modal_dart: git: url: https://github.com/nextchapterstudio/walletconnect_qrcode_modal_dart ref: main

RootSoft commented 1 year ago

Can you try with different fixed versions of algorand_dart?

algorand_dart: 2.0.0-dev.1

or

algorand_dart: 2.0.0-dev.11

There have been quite a few changes to the msgpack format lately, which might be causing it.

jasonhtpham commented 1 year ago

Hi @RootSoft, I just tested using those suggested versions above, but I still got the same error when I tried to sign the transaction.

jasonhtpham commented 1 year ago

Hi @RootSoft , do you have any updates on this?

RootSoft commented 1 year ago

@jasonhtpham Haven't been able to reproduce this. Can you whip up a bare, minimum reproducable example repository (on github) so I can test it out?

jasonhtpham commented 1 year ago

Hi, this is my AlgorandConnector.dart file:

import 'package:algorand_dart/algorand_dart.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:walletconnect_dart/walletconnect_dart.dart';
import 'package:walletconnect_qrcode_modal_dart/walletconnect_qrcode_modal_dart.dart';

import 'WalletConnector.dart'; // Abstract class

class AlgorandConnector implements WalletConnector {
  AlgorandConnector() {
    _connector = WalletConnectQrCodeModal(
      modalBuilder: (p0, uri, walletCallback, defaultModalWidget) {
        return defaultModalWidget.copyWith(
          walletListBuilder: (context, defaultWalletListWidget) {
            return defaultWalletListWidget.copyWith(
              shouldVerifyNativeLink: false,
            );
          },
        );
      },
      connector: WalletConnect(
        bridge: 'https://bridge.walletconnect.org',
        clientMeta: const PeerMeta(
          // <-- Meta data of your app appearing in the wallet when connecting
          name: 'Shark Tank DApp',
          description: 'Shark Tank DApp',
          url: 'https://walletconnect.org',
          icons: [
            'https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media'
          ],
        ),
      ),
    );

    _provider = AlgorandWalletConnectProvider(_connector.connector);
  }

  T? _tryCast<T>(dynamic value, {T? fallback}) {
    try {
      return (value as T);
    } on TypeError catch (_) {
      return fallback;
    }
  }

  @override
  Future<SessionStatus?> connect(BuildContext context) async {
    return await _connector.connect(context, chainId: 4160);
  }

  @override
  void registerListeners(
    OnConnectRequest? onConnect,
    OnSessionUpdate? onSessionUpdate,
    OnDisconnect? onDisconnect,
  ) =>
      _connector.registerListeners(
        onConnect: onConnect,
        onSessionUpdate: onSessionUpdate,
        onDisconnect: onDisconnect,
      );

  @override
  Future<String?> optInApp({
    required int appId,
  }) async {
    logger.i("Opting in to app: $appId");

    final sender =
        Address.fromAlgorandAddress(_connector.connector.session.accounts[0]);

    // Fetch the suggested transaction params
    final params = await _algorand.getSuggestedTransactionParams();

    // Build the transaction
    final transaction = await (ApplicationOptInTransactionBuilder()
          ..sender = sender
          ..noteText = 'Opt in to app $appId from $sender'
          ..applicationId = appId
          ..suggestedParams = params)
        .build();

    logger.i("Tx created: $transaction");

    try {
      // Sign the transaction
      final txBytes = Encoder.encodeMessagePack(transaction.toMessagePack());
      logger.i("Tx bytes: $txBytes");

      final signedBytes = await _provider.signTransaction(
        txBytes,
        params: {
          'message': 'Opt in to app $appId from $sender',
        },
      );

      logger.i("Tx signed: $signedBytes");

      // Broadcast the transaction
      final txId = await _algorand.sendRawTransactions(
        signedBytes,
        waitForConfirmation: true,
      );
      return txId;
    } catch (e) {
      final err = _tryCast(e, fallback: AlgorandException);
      if (err is AlgorandException) {
        AlgorandException error = err as AlgorandException;
        debugPrint('Error: ${error.message}');
      } else {
        debugPrint('Error: $e');
      }
      return null;
    }
  }

  @override
  Future<void> openWalletApp() async => await _connector.openWalletApp();

  @override
  Future<double> getBalance() async {
    final address = _connector.connector.session.accounts[0];
    // balance in microAlgos
    final balance = await _algorand.getBalance(address);
    return balance / BigInt.from(1000000);
  }

  @override
  bool validateAddress({required String address}) {
    try {
      Address.fromAlgorandAddress(address);
      return true;
    } catch (_) {
      return false;
    }
  }

  @override
  Future<void> disconnect() async => await _connector.killSession();

  @override
  bool get isConnected => _connector.connector.connected;

  @override
  String get faucetUrl => 'https://bank.testnet.algorand.network/';

  @override
  String get address => _connector.connector.session.accounts[0];

  @override
  WalletConnect get instance => _connector.connector;

  @override
  String get coinName => 'Algo';

  late final WalletConnectQrCodeModal _connector;
  late final AlgorandWalletConnectProvider _provider;
  late final _algorand = Algorand(
    options: AlgorandOptions(
        algodClient: AlgodClient(apiUrl: AlgoExplorer.TESTNET_ALGOD_API_URL)),
  );
}

This is how I use it:

           // Use the AlgorandConnector instance in the Provider
            BlockchainConnectionProvider blockchainProvider =
                Provider.of<BlockchainConnectionProvider>(context,
                    listen: false);

            await blockchainProvider.connect(context);

            if (!blockchainProvider.connector.isConnected) {
              throw Exception("Failed to connect wallet");
            }

// Failed at this step
            String? txId = await blockchainProvider.connector
                .optInApp(appId: tankDetails!.appId!);

            logger.i(txId);

The FormatException is caught in the optInApp function.

jasonhtpham commented 1 year ago

The issue was confirmed here https://github.com/nextchapterstudio/walletconnect_qrcode_modal_dart/issues/27#issuecomment-1493438023