DEV Community

Cover image for How to authorize the TON blockchain on DART using a wallet via TON Connect
Ivan Romanovich 🧐
Ivan Romanovich 🧐

Posted on

How to authorize the TON blockchain on DART using a wallet via TON Connect

Introduction

This article will show you how to use the darttonconnect library to quickly create an authorization and send transactions for the TON blockchain. Thus, you can quickly create cross-platform applications on Flutter for the TON blockchain.

Why do we need TON Connect?

In modern blockchain applications, there is a separation between wallets and decentralized applications.

Wallets provide a user interface for confirming transactions and securely storing users' cryptographic keys on their personal devices.

Applications are the user interface for smart contracts and do not have direct access to user funds. To carry out a transaction in a smart contract, the user needs to log in using the wallet and confirm all transactions initiated in the application in the wallet.

TON Connect is a protocol for the interaction of wallets and applications on the TON blockchain. The interaction goes through the bridge. The wallet "sends" events to the application via SSE.

You can use any wallet integrated into the protocol in TON Connect, for simplicity in this tutorial we will use Tonkeeper.

Install the wallet and switch it to the test network

In order to use it, you need a TON wallet, link to Tonkeeper: https://tonkeeper.com/

Since we will need to test transactions through Tonkeeper, it will be necessary to go to Tonkeeper and switch it to the test network, for this:

  • go to the settings and scroll down to the bottom to the inscription Tonkeeper version 3.0
  • click 6 times in a row quickly on the Tonkeeper icon above the inscription - a menu for developers will open
  • choose to switch to the test network in it
  • to get a test TON to your wallet on the test network, you need to use the bot: https://t.me/testgiver_ton_bot

Install libraries and create a project

Go to the directory where you will develop the application and create a project with the command:

flutter create . 
Enter fullscreen mode Exit fullscreen mode

In this tutorial, we will need the following libraries:

  • qr_flutter to generate QR code
  • darttonconnect to implement authorization logic through TON Connect

Install them with the commands:

flutter pub add qr_flutter
flutter pub add darttonconnect
Enter fullscreen mode Exit fullscreen mode

Let's assemble the skeleton of a single-page application

Since this tutorial is about authorization, we will not dwell on the framework of a single-page application.
Copy the code below into the main.dart file:

import 'package:flutter/material.dart';

import 'package:darttonconnect/exceptions.dart';
import 'package:darttonconnect/logger.dart';
import 'package:darttonconnect/ton_connect.dart';
import 'package:qr_flutter/qr_flutter.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        elevatedButtonTheme: ElevatedButtonThemeData(
            style: ButtonStyle(
                fixedSize: MaterialStateProperty.all(const Size(200, 30)))),
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  }
Enter fullscreen mode Exit fullscreen mode

Let's move on to the authorization logic

Our single page application will consist of one page and three buttons:

  • connect
  • Disconnect
  • Send transaction

Each of these buttons will call the corresponding function:

  • Connect - creating a QR code that we will scan with a wallet for authorization
  • Disconnect - disconnecting the wallet from the application
  • Send transaction - send a transaction

Let's add buttons and function calls (for a minimal design, the material design library will be used):

import 'package:flutter/material.dart';

import 'package:darttonconnect/exceptions.dart';
import 'package:darttonconnect/logger.dart';
import 'package:darttonconnect/ton_connect.dart';
import 'package:qr_flutter/qr_flutter.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        elevatedButtonTheme: ElevatedButtonThemeData(
            style: ButtonStyle(
                fixedSize: MaterialStateProperty.all(const Size(200, 30)))),
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
                onPressed: initialConnect,
                child: const Text('Create initial connect')),
            const SizedBox(height: 15),
            ElevatedButton(
                onPressed: disconnect, child: const Text('Disconnect')),
            const SizedBox(height: 15),
            ElevatedButton(onPressed: sendTrx, child: const Text('Sendtxes')),
            const SizedBox(height: 15),
            if (universalLink != null)
              QrImageView(
                data: universalLink!,
                version: QrVersions.auto,
                size: 320,
                gapless: false,
              )
          ],
        ),
      ),
    ));
  }
}
Enter fullscreen mode Exit fullscreen mode

Manifest

For authorization through TONConnect, you need a manifest file, it says:

  • app/site url it will be used to open the decentralized application after clicking on its icon in the wallet.
  • Website/App name
  • Application icon

The name and icon are needed so that the user in the wallet understands what he is connecting to.

In our case, we will use the test manifest located on the Github gist here: https://gist.githubusercontent.com/romanovichim/e81d599a6f3798bb9f74ab1970a8b376/raw/5f933bd5d24f5979b64ae88421e7849dd144efc1/gistfiletest.txt

Connection

The first thing to do is to create a connector through which the connection will occur. In the connector, we will use our manifest.

  // Initialize TonConnect.
  final TonConnect connector = TonConnect( 'https://gist.githubusercontent.com/romanovichim/e81d599a6f3798bb9f74ab1970a8b376/raw/43e00b0abc824ef272ac6d0f8083d21456602adf/gistfiletest.txt');
Enter fullscreen mode Exit fullscreen mode

In the TON Connect system, you can log in with any wallet that is connected to the TonConnect system, you can get the list like this:

final List wallets = await connector.getWallets();
Enter fullscreen mode Exit fullscreen mode

Since this is a simplified tutorial, we will only use Tonkeeper
Let's create a connection source and generate a link for authorization.

  // Initialize TonConnect.
  final TonConnect connector = TonConnect(
      'https://gist.githubusercontent.com/romanovichim/e81d599a6f3798bb9f74ab1970a8b376/raw/43e00b0abc824ef272ac6d0f8083d21456602adf/gistfiletest.txt');
  Map<String, String>? walletConnectionSource;
  String? universalLink;
  /// Create connection and generate QR code to connect a wallet.
  void initialConnect() async {
    const walletConnectionSource = {
      "universalUrl": 'https://app.tonkeeper.com/ton-connect',
      "bridgeUrl": 'https://bridge.tonapi.io/bridge'
    };

    final universalLink = await connector.connect(walletConnectionSource);
    updateQRCode(universalLink);

    connector.onStatusChange((walletInfo) {
      logger.i('Произошло изменение подключения');
    });
  }
Enter fullscreen mode Exit fullscreen mode

As you can see at the bottom of the code, if the connection changes, the connector will send the wallet information (meaning when we log in, we can see the wallet information). This is very convenient when debugging applications, for example, you can log every change.

To make the link convenient to use from a mobile device, add a QR code:

  void updateQRCode(String newData) {
    setState(() => universalLink = newData);
  }
Enter fullscreen mode Exit fullscreen mode

If we start the application now, then authorization will already work, but what if we logged in and left the site, and then returned, for this we can add a reconnect:

  @override
  void initState() {
    // Override default initState method to call restoreConnection
    // method after screen reloading.
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!connector.connected) {
        restoreConnection();
      }
    });
  }

  /// Restore connection from memory.
  void restoreConnection() {
    connector.restoreConnection();
  }
Enter fullscreen mode Exit fullscreen mode

Now let's add the disconnect function:

  /// Disconnect from current wallet.
  void disconnect() {
    if (connector.connected) {
      connector.disconnect();
    } else {
      logger.i("Сначала коннект, потом дисконект");
    }
  }
Enter fullscreen mode Exit fullscreen mode

When disconnecting, we check if the user is connected and then disconnect. It remains to send a transaction, sending occurs using sendTrx(). In this example, immediately after the hardcoded all the data, the final code:

 /// Send transaction with specified data.
void sendTrx() async {
if (!connector.connected) {
logger.i("Сначала коннект, потом дисконект");
} else {
const transaction = {
"validUntil": 1918097354,
"messages": [
{
"address":
"0:575af9fc97311a11f423a1926e7fa17a93565babfd65fe39d2e58b8ccb38c911",
"amount": "20000000",
}
]
};
  try {
    await connector.sendTransaction(transaction);
  } catch (e) {
    if (e is UserRejectsError) {
      logger.d(
          'You rejected the transaction. Please confirm it to send to the blockchain');
    } else {
      logger.d('Unknown error happened $e');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

}

Enter fullscreen mode Exit fullscreen mode




Final code


import 'package:flutter/material.dart';

import 'package:darttonconnect/exceptions.dart';
import 'package:darttonconnect/logger.dart';
import 'package:darttonconnect/ton_connect.dart';
import 'package:qr_flutter/qr_flutter.dart';

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

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

@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(const Size(200, 30)))),
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

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

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
// Initialize TonConnect.
final TonConnect connector = TonConnect(
'https://gist.githubusercontent.com/romanovichim/e81d599a6f3798bb9f74ab1970a8b376/raw/43e00b0abc824ef272ac6d0f8083d21456602adf/gistfiletest.txt');
Map<String, String>? walletConnectionSource;
String? universalLink;

@override
void initState() {
// Override default initState method to call restoreConnection
// method after screen reloading.
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!connector.connected) {
restoreConnection();
}
});
}

/// Create connection and generate QR code to connect a wallet.
void initialConnect() async {
const walletConnectionSource = {
"universalUrl": 'https://app.tonkeeper.com/ton-connect',
"bridgeUrl": 'https://bridge.tonapi.io/bridge'
};

final universalLink = await connector.connect(walletConnectionSource);
updateQRCode(universalLink);

connector.onStatusChange((walletInfo) {
  logger.i('Произошло изменение подключения');
});
Enter fullscreen mode Exit fullscreen mode

}

/// Restore connection from memory.
void restoreConnection() {
connector.restoreConnection();
}

void updateQRCode(String newData) {
setState(() => universalLink = newData);
}

/// Disconnect from current wallet.
void disconnect() {
if (connector.connected) {
connector.disconnect();
} else {
logger.i("Сначала коннект, потом дисконект");
}
}

/// Send transaction with specified data.
void sendTrx() async {
if (!connector.connected) {
logger.i("Сначала коннект, потом дисконект");
} else {
const transaction = {
"validUntil": 1918097354,
"messages": [
{
"address":
"0:575af9fc97311a11f423a1926e7fa17a93565babfd65fe39d2e58b8ccb38c911",
"amount": "20000000",
}
]
};

  try {
    await connector.sendTransaction(transaction);
  } catch (e) {
    if (e is UserRejectsError) {
      logger.d(
          'You rejected the transaction. Please confirm it to send to the blockchain');
    } else {
      logger.d('Unknown error happened $e');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: initialConnect,
child: const Text('Create initial connect')),
const SizedBox(height: 15),
ElevatedButton(
onPressed: disconnect, child: const Text('Disconnect')),
const SizedBox(height: 15),
ElevatedButton(onPressed: sendTrx, child: const Text('Sendtxes')),
const SizedBox(height: 15),
if (universalLink != null)
QrImageView(
data: universalLink!,
version: QrVersions.auto,
size: 320,
gapless: false,
)
],
),
),
));
}
}

Enter fullscreen mode Exit fullscreen mode




Conclusion

Thank you for your attention, link to the example from the article, link to the library. I write similar technical articles at https://t.me/ton_learn . I will be glad to your subscription.

Top comments (0)