Skip to content

Commit

Permalink
feat: update WalletConnect v2 and connect metamask (#2)
Browse files Browse the repository at this point in the history
* feat: update WalletConnect v2 and connect metamask

* chore: apply review

* fix: change url
  • Loading branch information
shiki-tak authored Jul 11, 2024
1 parent f007001 commit 8db3020
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 79 deletions.
2 changes: 2 additions & 0 deletions flutter_bird_app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ app.*.map.json

/lib/secrets.dart
/analysis_options.yaml

.env
7 changes: 4 additions & 3 deletions flutter_bird_app/lib/config.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const chainId = 5; // Goerli Testnet
const flutterBirdSkinsContractAddress = '0x387f544E4c3B2351d015dF57c30831Ad58D6C798';
const ipfsGatewayUrl = 'https://nftstorage.link/ipfs/';
const chainId = 1001; // Klaytn Testnet baobab
const flutterBirdSkinsContractAddress = '0xAca84dc56A05bbC7a335a74d9e13C91dfA2Ea16D';
const ipfsGatewayUrl = 'https://nftstorage.link/ipfs/'; //FIXME:
const alchemyNodeProviderUrl = 'https://eth-goerli.g.alchemy.com/v2/';
const walletConnectBridge = 'https://bridge.walletconnect.org';
const klaytnBaobabProviderUrl = 'https://public-en-baobab.klaytn.net';
144 changes: 84 additions & 60 deletions flutter_bird_app/lib/controller/authentication_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import 'dart:math' as math;

import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
import 'package:nonce/nonce.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:walletconnect_dart/walletconnect_dart.dart';
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';

import '../../model/account.dart';
import '../../model/wallet_provider.dart';
import '../config.dart';

/// Manages the authentication process and communication with crypto wallets
abstract class AuthenticationService {
Expand All @@ -38,11 +38,11 @@ class AuthenticationServiceImpl implements AuthenticationService {
late final List<WalletProvider> availableWallets;

final int operatingChain;
WalletConnect? _connector;
Web3App? _connector;
Function() onAuthStatusChanged;

@override
String get operatingChainName => operatingChain == 5 ? 'Goerli Testnet' : 'Chain $operatingChain';
String get operatingChainName => operatingChain == 1001 ? 'Klaytn Testnet' : 'Chain $operatingChain';

@override
Account? get authenticatedAccount => _authenticatedAccount;
Expand All @@ -51,12 +51,14 @@ class AuthenticationServiceImpl implements AuthenticationService {
@override
bool get isOnOperatingChain => currentChain == operatingChain;

int? get currentChain => _connector?.session.chainId;
SessionData? get currentSession => _connector?.sessions.getAll().firstOrNull;

int? get currentChain => int.tryParse(currentSession?.namespaces['eip155']?.accounts.first.split(':')[1] ?? '');

@override
bool get isAuthenticated => isConnected && authenticatedAccount != null;

bool get isConnected => _connector?.connected ?? false;
bool get isConnected => currentSession != null;

// The data to display in a QR Code for connections on Desktop / Browser.
@override
Expand Down Expand Up @@ -88,44 +90,60 @@ class AuthenticationServiceImpl implements AuthenticationService {

// Create a new session
if (!isConnected) {
await _connector?.createSession(
chainId: operatingChain,
onDisplayUri: (uri) async {
// Launches Wallet App
if (kIsWeb) {
webQrData = uri;
onAuthStatusChanged();
} else {
_launchWallet(wallet: walletProvider, uri: uri);
}
});

onAuthStatusChanged();
try {
ConnectResponse resp = await _connector!.connect(
requiredNamespaces: {
'eip155': RequiredNamespace(
chains: ['eip155:$operatingChain'],
methods: ['personal_sign'],
events: [],
),
},
);

Uri? uri = resp.uri;
if (uri != null) {
if (kIsWeb) {
webQrData = uri.toString();
onAuthStatusChanged();
} else {
_launchWallet(wallet: walletProvider, uri: uri.toString());
}
}

onAuthStatusChanged();
} catch (e) {
log('Error during connect: $e', name: 'AuthenticationService');
}
}
}

/// Send request to the users wallet to sign a message
/// User will be authenticated if the signature could be verified
Future<bool> _verifySignature({WalletProvider? walletProvider, String? address}) async {
int? chainId = _connector?.session.chainId;
if (address == null || chainId == null || !isOnOperatingChain) return false;
if (address == null || currentChain == null || !isOnOperatingChain) return false;

if (!kIsWeb) {
// Launch wallet app if on mobile
// Delay to make sure FlutterBird is in foreground before launching wallet app again
await Future.delayed(const Duration(seconds: 1));
_launchWallet(wallet: walletProvider, uri: _connector!.session.toUri());
// v2 doesn't have a uri property in currentSession so you need to get the proper URI.
_launchWallet(wallet: walletProvider, uri: 'wc:${currentSession!.topic}@2?relay-protocol=irn&symKey=${currentSession!.relay.protocol}');
}

log('Signing message...', name: 'AuthenticationService');

// Let Crypto Wallet sign custom message
String nonce = Nonce.generate(32, math.Random.secure());
String messageText = 'Please sign this message to authenticate with Flutter Bird.\nChallenge: $nonce';
final String signature = await _connector?.sendCustomRequest(method: 'personal_sign', params: [
messageText,
address,
]);
final String signature = await _connector!.request(
topic: currentSession!.topic,
chainId: 'eip155:$currentChain',
request: SessionRequestParams(
method: 'personal_sign',
params: [messageText, address],
),
);

// Check if signature is valid by recovering the exact address from message and signature
String recoveredAddress = EthSigUtil.recoverPersonalSignature(
Expand All @@ -135,54 +153,60 @@ class AuthenticationServiceImpl implements AuthenticationService {
bool isAuthenticated = recoveredAddress.toLowerCase() == address.toLowerCase();

// Set authenticated account
_authenticatedAccount = isAuthenticated ? Account(address: recoveredAddress, chainId: chainId) : null;
_authenticatedAccount = isAuthenticated ? Account(address: recoveredAddress, chainId: currentChain!) : null;

return isAuthenticated;
}

@override
unauthenticate() async {
await _connector?.killSession();
if (currentSession != null) {
await _connector?.disconnectSession(topic: currentSession!.topic, reason: Errors.getSdkError(Errors.USER_DISCONNECTED));
}
_authenticatedAccount = null;
_connector = null;
webQrData = null;
}

/// Creates a WalletConnect Instance
_createConnector({WalletProvider? walletProvider}) async {
Future<void> _createConnector({WalletProvider? walletProvider}) async {
// Create WalletConnect Connector
_connector = WalletConnect(
bridge: walletConnectBridge,
clientMeta: const PeerMeta(
name: 'Flutter Bird',
description: 'WalletConnect Developer App',
url: 'https://flutterbird.com',
icons: [
'https://raw.githubusercontent.com/Tonnanto/flutter-bird/v1.0/flutter_bird_app/assets/icon.png',
],
),
);
try {
_connector = await Web3App.createInstance(
projectId: dotenv.env['WALLET_CONNECT_PROJECT_ID']!,
metadata: const PairingMetadata(
name: 'Flutter Bird',
description: 'WalletConnect Developer App',
url: 'https://flutter-bird.netlify.app',
icons: [
'https://raw.githubusercontent.com/Tonnanto/flutter-bird/v1.0/flutter_bird_app/assets/icon.png',
],
),
);

// Subscribe to events
_connector?.on('connect', (session) async {
log('connected: ' + session.toString(), name: 'AuthenticationService');
String? address = _connector?.session.accounts.first;
webQrData = null;
final authenticated = await _verifySignature(walletProvider: walletProvider, address: address);
if (authenticated) log('authenticated successfully: ' + session.toString(), name: 'AuthenticationService');
onAuthStatusChanged();
});
_connector?.on('session_update', (payload) async {
log('session_update: ' + payload.toString(), name: 'AuthenticationService');
webQrData = null;
onAuthStatusChanged();
});
_connector?.on('disconnect', (session) {
log('disconnect: ' + session.toString(), name: 'AuthenticationService');
webQrData = null;
_authenticatedAccount = null;
onAuthStatusChanged();
});
// Subscribe to events
_connector?.onSessionConnect.subscribe((SessionConnect? session) async {
log('connected: ' + session.toString(), name: 'AuthenticationService');
String? address = session?.session.namespaces['eip155']?.accounts.first.split(':').last;
webQrData = null;
final authenticated = await _verifySignature(walletProvider: walletProvider, address: address);
if (authenticated) log('authenticated successfully: ' + session.toString(), name: 'AuthenticationService');
onAuthStatusChanged();
});
_connector?.onSessionUpdate.subscribe((SessionUpdate? payload) async {
log('session_update: ' + payload.toString(), name: 'AuthenticationService');
webQrData = null;
onAuthStatusChanged();
});
_connector?.onSessionDelete.subscribe((SessionDelete? session) {
log('disconnect: ' + session.toString(), name: 'AuthenticationService');
webQrData = null;
_authenticatedAccount = null;
onAuthStatusChanged();
});
} catch (e) {
log('Error during connector creation: $e', name: 'AuthenticationService');
}
}

Future<void> _launchWallet({
Expand Down
3 changes: 1 addition & 2 deletions flutter_bird_app/lib/controller/flutter_bird_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_bird/controller/authentication_service.dart';
import 'package:flutter_bird/controller/authorization_service.dart';
import 'package:flutter_bird/config.dart';
import 'package:flutter_bird/secrets.dart';

import '../model/account.dart';
import '../model/skin.dart';
Expand Down Expand Up @@ -36,7 +35,7 @@ class FlutterBirdController extends ChangeNotifier {
init() {
// Setting Up Web3 Connection
const String skinContractAddress = flutterBirdSkinsContractAddress;
String rpcUrl = alchemyNodeProviderUrl + alchemyApiKey;
String rpcUrl = klaytnBaobabProviderUrl;

_authenticationService = AuthenticationServiceImpl(
operatingChain: chainId,
Expand Down
3 changes: 3 additions & 0 deletions flutter_bird_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_bird/controller/flutter_bird_controller.dart';
import 'package:flutter_bird/view/main_menu_view.dart';
import 'package:provider/provider.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';


void main() {
dotenv.load(fileName: ".env");
runApp(const MyApp());
}

Expand Down
12 changes: 7 additions & 5 deletions flutter_bird_app/lib/view/authentication_popup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,13 @@ class _AuthenticationPopupState extends State<AuthenticationPopup> {
);
}

_buildQRView(String data) => QrImage(
data: data,
version: QrVersions.auto,
size: 200.0,
);
_buildQRView(String data) {
return QrImageView(
data: data,
version: QrVersions.auto,
size: 200.0,
);
}

_buildBackground() => Positioned.fill(
child: GestureDetector(
Expand Down
20 changes: 11 additions & 9 deletions flutter_bird_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_dotenv: ^5.1.0

web3dart: 2.4.1
walletconnect_dart: 0.0.11
web3dart: 2.7.3
walletconnect_flutter_v2: ^2.2.1
eth_sig_util: 0.0.9
nonce: 1.2.0
url_launcher: 6.1.5
url_launcher: 6.3.0
provider: 6.0.3
qr_flutter: 4.0.0
http: 0.13.5
qr_flutter: 4.1.0
http: 1.2.0

dev_dependencies:
flutter_launcher_icons: 0.10.0
flutter_lints: 1.0.0
shared_preferences: 2.0.12
build_runner: 2.1.11
flutter_launcher_icons: 0.13.1
flutter_lints: 4.0.0
shared_preferences: 2.2.3
build_runner: 2.4.11

flutter_icons:
android: "launcher_icon"
Expand All @@ -42,6 +43,7 @@ flutter:
assets:
- images/
- assets/
- .env
fonts:
- family: flappy
fonts:
Expand Down

0 comments on commit 8db3020

Please sign in to comment.