4

I want to make a Flutter application that can connect to Phantom wallet and then work on transactions and messages.

I was able to figure out the connection part, but I am unable to make the disconnection work.

In both cases, I get the same error:

{
    errorCode=-32603
    errorMessage="Failure to decrypt payload"
}

This is the code I used:

class MainApp extends StatefulWidget {
  const MainApp({Key? key}) : super(key: key);

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  final List<Widget> logs = [];
  late PrivateKey sk;
  late PublicKey pk;
  String walletAddr = "";
  String session = "";
  late Box sharedSecret;

  @override
  void initState() {
    super.initState();
    sk = PrivateKey.generate();
    pk = sk.publicKey;
  }

  void _connect() async {
    Uri url = Uri(
      scheme: 'https',
      host: 'phantom.app',
      path: '/ul/v1/connect',
      queryParameters: {
        'dapp_encryption_public_key': base58.encode(pk.asTypedList),
        'cluster': "devnet",
        'app_url': "https://phantom.app",
        'redirect_link': 'flutterphantom://deeplink.connect',
      },
    );
    launchUrl(
      url,
      mode: LaunchMode.externalApplication,
    );
  }

  _onConnect(Map params) async {
    sharedSecret = Box(
      myPrivateKey: sk,
      theirPublicKey: PublicKey(
        base58.decode(
          params["phantom_encryption_public_key"],
        ),
      ),
    );

    final decryptedData = sharedSecret.decrypt(
      ByteList(base58.decode(
        params["data"],
      )),
      nonce: base58.decode(params["nonce"]),
    );

    Map data = const JsonDecoder().convert(String.fromCharCodes(decryptedData));

    session = data["session"];
    walletAddr = data["public_key"];

    logs.add(
      Text(
        "Wallet address: ${data["public_key"].toString().substring(0, 16)}...",
        style: const TextStyle(
          color: Colors.white,
        ),
      ),
    );
  }

  void _disconnect() async {
    JsonEncoder encoder = const JsonEncoder();
    Map payload = {
      "session": session,
    };
    String payloadJson = encoder.convert(payload);

    final encryptedMsg = sharedSecret.encrypt(
      Uint8List.fromList(payloadJson.codeUnits),
    );

    Uri url = Uri(
      scheme: 'https',
      host: 'phantom.app',
      path: '/ul/v1/disconnect',
      queryParameters: {
        'dapp_encryption_public_key': base58.encode(pk.asTypedList),
        'nonce': base58.encode(encryptedMsg.nonce.toUint8List()),
        'payload': base58.encode(encryptedMsg.toUint8List()),
        'redirect_link': 'flutterphantom://deeplink.disconnect',
      },
    );
    launchUrl(
      url,
      mode: LaunchMode.externalApplication,
    );
  }

  @override
  Widget build(BuildContext context) {
    DeepLinkProvider provider = DeepLinkProvider();
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("Flutter Phantom Deeplinking"),
        ),
        body: Provider<DeepLinkProvider>(
          create: (context) => provider,
          dispose: (context, provider) => provider.dispose(),
          child: StreamBuilder<String>(
            stream: provider.state,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                Uri redirectedUri = Uri.parse(snapshot.data!);
                Map params = redirectedUri.queryParameters;
                if (params.containsKey("errorCode")) {
                  print(params["errorMessage"]);
                } else {
                  switch (redirectedUri.host.split('.')[1]) {
                    case 'connect':
                      _onConnect(params);
                      break;
                    case 'disconnect':
                      print('disconnected');
                      break;
                    default:
                  }
                }
              }
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Container(
                      width: MediaQuery.of(context).size.width,
                      height: 300,
                      decoration: const BoxDecoration(
                        color: Colors.black,
                      ),
                      child: SingleChildScrollView(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              "LOGS:",
                              style: TextStyle(
                                color: Colors.white,
                              ),
                            ),
                            ...logs,
                          ],
                        ),
                      ),
                    ),
                    ElevatedButton(
                      onPressed: _connect,
                      child: const Text("Connect Phantom"),
                    ),
                    ElevatedButton(
                      onPressed: () => walletAddr == ""
                          ? ScaffoldMessenger.of(context).showSnackBar(
                              const SnackBar(
                                content: Text(
                                  "Please connect wallet first",
                                ),
                                duration: Duration(seconds: 2),
                              ),
                            )
                          : _disconnect(),
                      child: const Text("Disconnect"),
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

I followed the encryption steps of this React Native Expo code. But for some reason, Phantom cannot decrypt the session payload I create.

1
  • 1
    If you have a working phantom example for flutter via deep-link, Can you please put the demo example code on the GitHub repo or create a gist? That will be really helpful to other members of the community. You can also create a blog for the same. Commented Sep 2, 2022 at 13:44

2 Answers 2

2

Ok so instead of this:

final encryptedMsg = sharedSecret.encrypt(
  Uint8List.fromList(payloadJson.codeUnits),
);

it worked when I added .cipherText and used a nonce

var nonce = PineNaClUtils.randombytes(24);

final encryptedMsg = _box.encrypt(
  _encoder.convert(payload).codeUnits.toUint8List(),
  nonce: nonce,
).cipherText;
0
2

You can use my package for flutter flutter_phantom

import 'package:flutter_phantom/flutter_phantom.dart';

initialise required Parameters.

final FlutterPhantom phantom = FlutterPhantom(
appUrl: "https://phantom.app",
deepLink: "app://mydeapp",
);

An full example of how to use this package (Android,Ios) flutter_phantom-demo.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.