How to authorize the TON blockchain on DART using a wallet via TON Connect
Tags: blockchain, tutorial, dart, flutter
Published: 2023-06-15 15:08:27

## Introduction

This article will show you how to use the [darttonconnect](https://pub.dev/packages/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](https://tonkeeper.com/).

## 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 . 

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
	
## 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> {
	
	  }

## 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,
				  )
			  ],
			),
		  ),
		));
	  }
	}

### 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');

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();

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('Произошло изменение подключения');
		});
	  }

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);
	  }
	  

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();
	  }

Now let's add the disconnect function:

	  /// Disconnect from current wallet.
	  void disconnect() {
		if (connector.connected) {
		  connector.disconnect();
		} else {
		  logger.i("Сначала коннект, потом дисконект");
		}
	  }
	  
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');
			}
		  }
		}
	  }

## 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('Произошло изменение подключения');
		});
	  }

	  /// 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');
			}
		  }
		}
	  }

	  @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,
				  )
			  ],
			),
		  ),
		));
	  }
	}

## Conclusion

Thank you for your attention, [link](https://pub.dev/packages/darttonconnect) to the example from the article, [link](https://github.com/romanovichim/dartExampleForTONConnect) to the library. I write similar technical articles at https://t.me/ton_learn . I will be glad to your subscription.