├── .gitignore ├── .metadata ├── Makefile ├── README.md ├── analysis_options.yaml ├── lib ├── js │ ├── phantom.dart │ └── solana_web3 │ │ ├── connection.dart │ │ ├── fee_calculator.dart │ │ ├── public_key.dart │ │ └── transaction.dart ├── main.dart └── web_wallet.dart ├── package-lock.json ├── package.json ├── pubspec.lock ├── pubspec.yaml ├── src ├── index.ts └── phantom.ts ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html ├── manifest.json ├── wallet.js └── wallet.js.LICENSE.txt └── webpack.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | # Node related 49 | node_modules/ 50 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 3595343e20a61ff16d14e8ecc25f364276bb1b8b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: webpack build_web debug profile_build run 2 | 3 | 4 | webpack: 5 | npm run webpack 6 | 7 | build_web: 8 | flutter build web 9 | 10 | debug: 11 | npm run webpack && \ 12 | flutter run -d chrome 13 | 14 | human_readable_build: 15 | npm run webpack && \ 16 | flutter build web --profile --dart-define=Dart2jsOptimization=O0 && \ 17 | python -m http.server 8080 --directory build/web 18 | 19 | run: 20 | npm run webpack && \ 21 | flutter build web && \ 22 | python -m http.server 8080 --directory build/web 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Proof of Concept for Solana Dapps on Flutter 2 | 3 | ##### Install JS Packages with 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ##### Build JS with 9 | ``` 10 | npm run webpack 11 | ``` 12 | 13 | Then build Flutter like a normal web project with `flutter run -d chrome` or `flutter build web`. 14 | 15 | See Makefile for command shortcuts. 16 | 17 | 18 | ## General Approach 19 | 20 | We create a Node project that imports the `@solana/web3.js` library and also takes advantage of the `window.solana` global that is made available by Phantom wallet extension. 21 | 22 | Any relevant JS functionality is ported to Dart via the FFI that comes with Dart and which is compatible with Flutter Web projects. 23 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /lib/js/phantom.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:js/js.dart'; 4 | import 'package:sol_dapp_flutter_test/js/solana_web3/transaction.dart'; 5 | import 'package:sol_dapp_flutter_test/js/solana_web3/public_key.dart'; 6 | 7 | @JS('wallet.phantom.connect') 8 | external Future connect(); 9 | 10 | @JS('wallet.phantom.disconnect') 11 | external Future disconnect(); 12 | 13 | @JS('wallet.phantom.isPhantomInstalled') 14 | external bool? isPhantomInstalled(); 15 | 16 | @JS('wallet.phantom.signTransaction') 17 | external Future signTransaction(Transaction tx); 18 | 19 | @anonymous 20 | @JS('wallet.phantom.SignedMessage') 21 | class SignedMessage { 22 | external factory SignedMessage({Uint8List signature, PublicKey publicKey}); 23 | external Uint8List get signature; 24 | external PublicKey get publicKey; 25 | } 26 | 27 | @JS('wallet.phantom.signMessage') 28 | external Future signMessage(String msg); 29 | -------------------------------------------------------------------------------- /lib/js/solana_web3/connection.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:js/js.dart'; 4 | import 'package:sol_dapp_flutter_test/js/solana_web3/fee_calculator.dart'; 5 | import 'package:sol_dapp_flutter_test/js/solana_web3/public_key.dart'; 6 | 7 | @JS('wallet.solanaWeb3.clusterApiUrl') 8 | external String clusterApiUrl(String network); 9 | 10 | @anonymous 11 | @JS() 12 | class GetRecentBlockhashResponse { 13 | external factory GetRecentBlockhashResponse({String blockhash, FeeCalculator feeCalculator}); 14 | external String get blockhash; 15 | external FeeCalculator get feeCalculator; 16 | } 17 | 18 | @JS('wallet.solanaWeb3.Connection') 19 | class Connection { 20 | external Connection(String url); 21 | external Future getAccountInfo(PublicKey publicKey, String? commitment); 22 | external Future getRecentBlockhash(); 23 | external Future sendRawTransaction(Uint8List rawTransaction); 24 | external Future requestAirdrop(PublicKey to, int lamports); 25 | external Future confirmTransaction(String signature); 26 | } 27 | 28 | @anonymous 29 | @JS('wallet.solanaWeb3.AccountInfo') 30 | class AccountInfo { 31 | external factory AccountInfo({int lamports, bool executable, PublicKey owner, dynamic data}); 32 | external int get lamports; 33 | external PublicKey get owner; 34 | external bool get executable; 35 | external dynamic get data; 36 | } 37 | -------------------------------------------------------------------------------- /lib/js/solana_web3/fee_calculator.dart: -------------------------------------------------------------------------------- 1 | import 'package:js/js.dart'; 2 | 3 | @anonymous 4 | @JS() 5 | class FeeCalculator { 6 | external factory FeeCalculator({int lamportsPerSignature}); 7 | } 8 | -------------------------------------------------------------------------------- /lib/js/solana_web3/public_key.dart: -------------------------------------------------------------------------------- 1 | import 'package:js/js.dart'; 2 | 3 | @JS('wallet.solanaWeb3.PublicKey') 4 | class PublicKey { 5 | external PublicKey(String value); 6 | external String toBase58(); 7 | external String toString(); 8 | } 9 | -------------------------------------------------------------------------------- /lib/js/solana_web3/transaction.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:js/js.dart'; 3 | import 'package:sol_dapp_flutter_test/js/solana_web3/public_key.dart'; 4 | 5 | @anonymous 6 | @JS('wallet.solanaWeb3.AccountMeta') 7 | class AccountMeta { 8 | external factory AccountMeta({PublicKey pubkey, bool isSigner, bool isWritable}); 9 | external PublicKey get pubkey; 10 | external bool get isSigner; 11 | external bool get isWritable; 12 | } 13 | 14 | @anonymous 15 | @JS('wallet.solanaWeb3.TransactionInstruction') 16 | class TransactionInstruction { 17 | external factory TransactionInstruction( 18 | {PublicKey programId, Uint8List data, List keys}); 19 | } 20 | 21 | @anonymous 22 | @JS() 23 | class FeePayer { 24 | external factory FeePayer({PublicKey feePayer}); 25 | } 26 | 27 | @JS('wallet.solanaWeb3.Transaction') 28 | class Transaction { 29 | external Transaction(FeePayer); 30 | external Transaction add(TransactionInstruction instruction); 31 | external Uint8List serialize(); 32 | external set recentBlockhash(String value); 33 | external String get recentBlockhash; 34 | } 35 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:js' as js; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_hooks/flutter_hooks.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | import 'package:sol_dapp_flutter_test/web_wallet.dart'; 7 | 8 | void main() { 9 | runApp(ProviderScope(child: const WidgetTree())); 10 | } 11 | 12 | class WidgetTree extends HookConsumerWidget { 13 | const WidgetTree(); 14 | 15 | Widget _pleaseInstallPhantom() { 16 | return Center(child: Text("Please Install Phantom")); 17 | } 18 | 19 | Widget _pleaseConnectWallet(BuildContext context, WidgetRef ref) { 20 | return Center( 21 | child: Column( 22 | mainAxisAlignment: MainAxisAlignment.center, 23 | children: [ 24 | Text("Please Connect to Phantom"), 25 | SizedBox(height: 30), 26 | ElevatedButton( 27 | child: Text("Connect"), 28 | onPressed: () => ref.read(wallet.notifier).connectWallet(), 29 | ) 30 | ], 31 | )); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context, WidgetRef ref) { 36 | final pubkey = ref.watch(wallet); 37 | final browserHasPhantom = ref.watch(hasPhantom); 38 | // Determine what to show depending on Phantom wallet install/connection 39 | late String title; 40 | late Widget body; 41 | if (!browserHasPhantom) { 42 | title = "Please Install Phantom"; 43 | body = _pleaseInstallPhantom(); 44 | } else { 45 | if (pubkey != null) { 46 | title = "Solana Dapp Flutter Demo"; 47 | body = const HomePage(); 48 | } else { 49 | title = "Please Connect to Phantom"; 50 | body = _pleaseConnectWallet(context, ref); 51 | } 52 | } 53 | return MaterialApp( 54 | title: 'Solana Dapp Flutter Demo', 55 | theme: ThemeData.dark(), 56 | home: Scaffold( 57 | appBar: AppBar( 58 | title: Text(title), 59 | //backgroundColor: Color.fromARGB(255, 78, 68, 206), 60 | ), 61 | body: body, 62 | )); 63 | } 64 | } 65 | 66 | class HomePage extends HookConsumerWidget { 67 | const HomePage(); 68 | 69 | Widget _info(String title, String content, BuildContext context) { 70 | return Row(mainAxisSize: MainAxisSize.min, children: [ 71 | Text("$title: ", style: Theme.of(context).textTheme.headline5), 72 | Container( 73 | padding: EdgeInsets.all(10), 74 | decoration: BoxDecoration( 75 | borderRadius: BorderRadius.all(Radius.circular(5)), 76 | color: Theme.of(context).primaryColor, 77 | ), 78 | child: Text(content, 79 | overflow: TextOverflow.fade, style: Theme.of(context).textTheme.headline5)) 80 | ]); 81 | } 82 | 83 | @override 84 | Widget build(BuildContext context, WidgetRef ref) { 85 | final lamports = ref.watch(accountInfo).state?.lamports.toString() ?? ''; 86 | final address = ref.watch(wallet)?.toBase58() ?? ''; 87 | //final previousTx = ref.watch(previousTxid).state; 88 | final previousTx = useState(''); 89 | final url = previousTx.value.isNotEmpty 90 | ? 'https://explorer.solana.com/tx/$previousTx?cluster=devnet' 91 | : ''; 92 | final previousSignedPlaintext = useState(''); 93 | return Container( 94 | padding: EdgeInsets.all(15), 95 | //constraints: BoxConstraints(maxWidth: 600), 96 | child: Column( 97 | mainAxisAlignment: MainAxisAlignment.center, 98 | crossAxisAlignment: CrossAxisAlignment.start, 99 | children: [ 100 | _info("Address", address, context), 101 | SizedBox(height: 10), 102 | _info("Balance", lamports, context), 103 | SizedBox(height: 10), 104 | ElevatedButton( 105 | child: Text("Airdrop"), 106 | onPressed: () => ref.read(wallet.notifier).requestAirdrop(), 107 | ), 108 | SizedBox(height: 30), 109 | ElevatedButton( 110 | child: Text("Refresh Account Info"), 111 | onPressed: () => ref.read(wallet.notifier).refreshAccountInfo(), 112 | ), 113 | SizedBox(height: 30), 114 | ElevatedButton( 115 | child: Text("Send Memo Transaction"), 116 | onPressed: () => ref 117 | .read(wallet.notifier) 118 | .confirmAndSendMemo("Hello world") 119 | .then((txid) => previousTx.value = txid), 120 | ), 121 | SizedBox(height: 10), 122 | _info("Previous Transaction", previousTx.value, context), 123 | SizedBox(height: 10), 124 | ElevatedButton( 125 | child: Text("View in Explorer: "), 126 | onPressed: () => url.isNotEmpty ? js.context.callMethod('open', [url]) : null), 127 | SizedBox(height: 30), 128 | _info("Previous Signed Message", previousSignedPlaintext.value, context), 129 | SizedBox(height: 10), 130 | ElevatedButton( 131 | child: Text("Sign utf8 plaintext"), 132 | onPressed: () => ref.read(wallet.notifier).signPlaintext("Hello world").then( 133 | (signedMessage) => 134 | previousSignedPlaintext.value = base64.encode(signedMessage.signature))) 135 | ], 136 | ), 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/web_wallet.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | import 'package:js/js_util.dart'; 4 | import 'package:sol_dapp_flutter_test/js/solana_web3/connection.dart' as conn; 5 | import 'package:sol_dapp_flutter_test/js/solana_web3/public_key.dart'; 6 | import 'package:sol_dapp_flutter_test/js/solana_web3/transaction.dart'; 7 | import 'package:sol_dapp_flutter_test/js/phantom.dart' as phantom; 8 | import 'dart:convert' show utf8; 9 | 10 | final accountInfo = StateProvider((ref) => null); 11 | final hasPhantom = Provider((ref) => phantom.isPhantomInstalled() ?? false); 12 | 13 | /// Access Service classes through Provider for singleton and mocking 14 | final wallet = StateNotifierProvider((ref) => WebWallet(ref.read)); 15 | 16 | /// All our Solana-related stuff 17 | class WebWallet extends StateNotifier { 18 | Reader read; 19 | // Hardcoding devnet 20 | final _conn = conn.Connection(conn.clusterApiUrl('devnet')); 21 | 22 | WebWallet(this.read) : super(null); 23 | 24 | Future connectWallet() async { 25 | print("Connecting wallet..."); 26 | if (!read(hasPhantom)) { 27 | throw Exception("Phantom is not installed"); 28 | } 29 | final pubkey = await promiseToFuture(phantom.connect()); 30 | state = pubkey; 31 | refreshAccountInfo(); 32 | return pubkey; 33 | } 34 | 35 | /// Create a transaction from scratch, sign with Phantom, send it. 36 | Future confirmAndSendMemo(String msg) async { 37 | // Prepare a tx instruction 38 | final meta = [AccountMeta(pubkey: state!, isSigner: true, isWritable: false)]; 39 | final memoProgram = PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"); 40 | final data = Uint8List.fromList(utf8.encode(msg)); 41 | final instruction = TransactionInstruction(data: data, programId: memoProgram, keys: meta); 42 | // Add it to a new tx 43 | final tx = Transaction(FeePayer(feePayer: state!)); 44 | tx.add(instruction); 45 | // Fetch and assign a recent blockhash, request signature 46 | final recentBlockhash = await promiseToFuture(_conn.getRecentBlockhash()); 47 | tx.recentBlockhash = recentBlockhash.blockhash; 48 | final signed = await promiseToFuture(phantom.signTransaction(tx)); 49 | // Serialize and send 50 | final serialized = signed.serialize(); 51 | final txid = await promiseToFuture(_conn.sendRawTransaction(serialized)); 52 | return txid; 53 | } 54 | 55 | Future refreshAccountInfo() async { 56 | conn.AccountInfo? info = await promiseToFuture(_conn.getAccountInfo(state!, null)); 57 | if (info == null) { 58 | final txid = await _conn.requestAirdrop(state!, 100000000); 59 | await _conn.confirmTransaction(txid); 60 | } 61 | info = await promiseToFuture(_conn.getAccountInfo(state!, null)); 62 | if (info == null) { 63 | throw Exception("Invalid address, could not locate balance or perform airdrop"); 64 | } 65 | read(accountInfo).state = info; 66 | return info; 67 | } 68 | 69 | Future requestAirdrop({int lamports: 1000000000}) async { 70 | await _conn.requestAirdrop(state!, lamports); 71 | } 72 | 73 | Future signPlaintext(String plaintext) async { 74 | return await promiseToFuture(phantom.signMessage(plaintext)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sol_dapp_flutter_test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "webpack": "webpack" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/preset-env": "^7.16.0", 15 | "@babel/preset-typescript": "^7.16.0", 16 | "babel-loader": "^8.2.3", 17 | "copy-webpack-plugin": "^9.1.0", 18 | "expose-loader": "^3.1.0", 19 | "ts-node": "^10.4.0", 20 | "webpack": "^5.64.0", 21 | "webpack-cli": "^4.9.1" 22 | }, 23 | "dependencies": { 24 | "@solana/web3.js": "^1.30.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.4" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.2.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_hooks: 66 | dependency: "direct main" 67 | description: 68 | name: flutter_hooks 69 | url: "https://pub.dartlang.org" 70 | source: hosted 71 | version: "0.18.1" 72 | flutter_lints: 73 | dependency: "direct dev" 74 | description: 75 | name: flutter_lints 76 | url: "https://pub.dartlang.org" 77 | source: hosted 78 | version: "1.0.4" 79 | flutter_riverpod: 80 | dependency: transitive 81 | description: 82 | name: flutter_riverpod 83 | url: "https://pub.dartlang.org" 84 | source: hosted 85 | version: "1.0.0-dev.7" 86 | flutter_test: 87 | dependency: "direct dev" 88 | description: flutter 89 | source: sdk 90 | version: "0.0.0" 91 | freezed_annotation: 92 | dependency: transitive 93 | description: 94 | name: freezed_annotation 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.14.3" 98 | hooks_riverpod: 99 | dependency: "direct main" 100 | description: 101 | name: hooks_riverpod 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.0.0-dev.7" 105 | js: 106 | dependency: "direct main" 107 | description: 108 | name: js 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.6.3" 112 | json_annotation: 113 | dependency: transitive 114 | description: 115 | name: json_annotation 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "4.3.0" 119 | lints: 120 | dependency: transitive 121 | description: 122 | name: lints 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.0.1" 126 | matcher: 127 | dependency: transitive 128 | description: 129 | name: matcher 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "0.12.10" 133 | meta: 134 | dependency: transitive 135 | description: 136 | name: meta 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "1.7.0" 140 | path: 141 | dependency: transitive 142 | description: 143 | name: path 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "1.8.0" 147 | riverpod: 148 | dependency: "direct main" 149 | description: 150 | name: riverpod 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "1.0.0-dev.7" 154 | sky_engine: 155 | dependency: transitive 156 | description: flutter 157 | source: sdk 158 | version: "0.0.99" 159 | source_span: 160 | dependency: transitive 161 | description: 162 | name: source_span 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.8.1" 166 | stack_trace: 167 | dependency: transitive 168 | description: 169 | name: stack_trace 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.10.0" 173 | state_notifier: 174 | dependency: transitive 175 | description: 176 | name: state_notifier 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "0.7.1" 180 | stream_channel: 181 | dependency: transitive 182 | description: 183 | name: stream_channel 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "2.1.0" 187 | string_scanner: 188 | dependency: transitive 189 | description: 190 | name: string_scanner 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.1.0" 194 | term_glyph: 195 | dependency: transitive 196 | description: 197 | name: term_glyph 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.2.0" 201 | test_api: 202 | dependency: transitive 203 | description: 204 | name: test_api 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "0.4.2" 208 | typed_data: 209 | dependency: transitive 210 | description: 211 | name: typed_data 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.3.0" 215 | vector_math: 216 | dependency: transitive 217 | description: 218 | name: vector_math 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "2.1.0" 222 | sdks: 223 | dart: ">=2.14.0 <3.0.0" 224 | flutter: ">=1.20.0" 225 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sol_dapp_flutter_test 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | 33 | 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | cupertino_icons: ^1.0.2 37 | flutter_hooks: ^0.18.1 38 | hooks_riverpod: 1.0.0-dev.7 39 | riverpod: 1.0.0-dev.7 40 | js: ^0.6.3 41 | 42 | 43 | dev_dependencies: 44 | flutter_test: 45 | sdk: flutter 46 | 47 | # The "flutter_lints" package below contains a set of recommended lints to 48 | # encourage good coding practices. The lint set provided by the package is 49 | # activated in the `analysis_options.yaml` file located at the root of your 50 | # package. See that file for information about deactivating specific lint 51 | # rules and activating additional ones. 52 | flutter_lints: ^1.0.0 53 | 54 | # For information on the generic Dart part of this file, see the 55 | # following page: https://dart.dev/tools/pub/pubspec 56 | 57 | # The following section is specific to Flutter. 58 | flutter: 59 | 60 | # The following line ensures that the Material Icons font is 61 | # included with your application, so that you can use the icons in 62 | # the material Icons class. 63 | uses-material-design: true 64 | 65 | # To add assets to your application, add an assets section, like this: 66 | # assets: 67 | # - images/a_dot_burr.jpeg 68 | # - images/a_dot_ham.jpeg 69 | 70 | # An image asset can refer to one or more resolution-specific "variants", see 71 | # https://flutter.dev/assets-and-images/#resolution-aware. 72 | 73 | # For details regarding adding assets from package dependencies, see 74 | # https://flutter.dev/assets-and-images/#from-packages 75 | 76 | # To add custom fonts to your application, add a fonts section here, 77 | # in this "flutter" section. Each entry in this list should have a 78 | # "family" key with the font family name, and a "fonts" key with a 79 | # list giving the asset and other descriptors for the font. For 80 | # example: 81 | # fonts: 82 | # - family: Schyler 83 | # fonts: 84 | # - asset: fonts/Schyler-Regular.ttf 85 | # - asset: fonts/Schyler-Italic.ttf 86 | # style: italic 87 | # - family: Trajan Pro 88 | # fonts: 89 | # - asset: fonts/TrajanPro.ttf 90 | # - asset: fonts/TrajanPro_Bold.ttf 91 | # weight: 700 92 | # 93 | # For details regarding fonts from package dependencies, 94 | # see https://flutter.dev/custom-fonts/#from-packages 95 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export const solanaWeb3 = require('@solana/web3.js'); 2 | 3 | export const phantom = require('./phantom.ts'); 4 | -------------------------------------------------------------------------------- /src/phantom.ts: -------------------------------------------------------------------------------- 1 | const solanaWeb3 = require('@solana/web3.js'); 2 | 3 | export async function connect(): solanaWeb3.PublicKey { 4 | const resp = await window.solana.connect(); 5 | return new solanaWeb3.PublicKey(resp.publicKey.toString()); 6 | } 7 | 8 | export function disconnect() { 9 | window.solana.disconnect(); 10 | } 11 | 12 | export function isPhantomInstalled(): bool { 13 | return window.solana && window.solana.isPhantom; 14 | } 15 | 16 | export async function signTransaction(tx: solanaWeb3.Transaction): solanaWeb3.Transaction { 17 | return await window.solana.signTransaction(tx); 18 | } 19 | 20 | type SignedMessage = { 21 | signature: string, 22 | publicKey: solanaWeb3.PublicKey 23 | } 24 | 25 | export async function signMessage(msg: string): SignedMessage { 26 | const encodedMsg = new TextEncoder().encode(msg); 27 | return await window.solana.signMessage(encodedMsg, 'utf8'); 28 | } 29 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Auguron/sol_dapp_flutter_example/dc3078dbb351b3d7806df32e8fe7e65ed1852d05/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Auguron/sol_dapp_flutter_example/dc3078dbb351b3d7806df32e8fe7e65ed1852d05/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Auguron/sol_dapp_flutter_example/dc3078dbb351b3d7806df32e8fe7e65ed1852d05/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Auguron/sol_dapp_flutter_example/dc3078dbb351b3d7806df32e8fe7e65ed1852d05/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Auguron/sol_dapp_flutter_example/dc3078dbb351b3d7806df32e8fe7e65ed1852d05/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | sol_dapp_flutter_test 17 | 18 | 19 | 20 | 21 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sol_dapp_flutter_test", 3 | "short_name": "sol_dapp_flutter_test", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /web/wallet.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * The buffer module from node.js, for the browser. 3 | * 4 | * @author Feross Aboukhadijeh 5 | * @license MIT 6 | */ 7 | 8 | /*! 9 | Copyright (C) 2013-2017 by Andrea Giammarchi - @WebReflection 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | 29 | */ 30 | 31 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ 32 | 33 | /*! safe-buffer. MIT License. Feross Aboukhadijeh */ 34 | 35 | /** 36 | * Support for translating between Uint8Array instances and JavaScript 37 | * native types. 38 | * 39 | * {@link module:Layout~Layout|Layout} is the basis of a class 40 | * hierarchy that associates property names with sequences of encoded 41 | * bytes. 42 | * 43 | * Layouts are supported for these scalar (numeric) types: 44 | * * {@link module:Layout~UInt|Unsigned integers in little-endian 45 | * format} with {@link module:Layout.u8|8-bit}, {@link 46 | * module:Layout.u16|16-bit}, {@link module:Layout.u24|24-bit}, 47 | * {@link module:Layout.u32|32-bit}, {@link 48 | * module:Layout.u40|40-bit}, and {@link module:Layout.u48|48-bit} 49 | * representation ranges; 50 | * * {@link module:Layout~UIntBE|Unsigned integers in big-endian 51 | * format} with {@link module:Layout.u16be|16-bit}, {@link 52 | * module:Layout.u24be|24-bit}, {@link module:Layout.u32be|32-bit}, 53 | * {@link module:Layout.u40be|40-bit}, and {@link 54 | * module:Layout.u48be|48-bit} representation ranges; 55 | * * {@link module:Layout~Int|Signed integers in little-endian 56 | * format} with {@link module:Layout.s8|8-bit}, {@link 57 | * module:Layout.s16|16-bit}, {@link module:Layout.s24|24-bit}, 58 | * {@link module:Layout.s32|32-bit}, {@link 59 | * module:Layout.s40|40-bit}, and {@link module:Layout.s48|48-bit} 60 | * representation ranges; 61 | * * {@link module:Layout~IntBE|Signed integers in big-endian format} 62 | * with {@link module:Layout.s16be|16-bit}, {@link 63 | * module:Layout.s24be|24-bit}, {@link module:Layout.s32be|32-bit}, 64 | * {@link module:Layout.s40be|40-bit}, and {@link 65 | * module:Layout.s48be|48-bit} representation ranges; 66 | * * 64-bit integral values that decode to an exact (if magnitude is 67 | * less than 2^53) or nearby integral Number in {@link 68 | * module:Layout.nu64|unsigned little-endian}, {@link 69 | * module:Layout.nu64be|unsigned big-endian}, {@link 70 | * module:Layout.ns64|signed little-endian}, and {@link 71 | * module:Layout.ns64be|unsigned big-endian} encodings; 72 | * * 32-bit floating point values with {@link 73 | * module:Layout.f32|little-endian} and {@link 74 | * module:Layout.f32be|big-endian} representations; 75 | * * 64-bit floating point values with {@link 76 | * module:Layout.f64|little-endian} and {@link 77 | * module:Layout.f64be|big-endian} representations; 78 | * * {@link module:Layout.const|Constants} that take no space in the 79 | * encoded expression. 80 | * 81 | * and for these aggregate types: 82 | * * {@link module:Layout.seq|Sequence}s of instances of a {@link 83 | * module:Layout~Layout|Layout}, with JavaScript representation as 84 | * an Array and constant or data-dependent {@link 85 | * module:Layout~Sequence#count|length}; 86 | * * {@link module:Layout.struct|Structure}s that aggregate a 87 | * heterogeneous sequence of {@link module:Layout~Layout|Layout} 88 | * instances, with JavaScript representation as an Object; 89 | * * {@link module:Layout.union|Union}s that support multiple {@link 90 | * module:Layout~VariantLayout|variant layouts} over a fixed 91 | * (padded) or variable (not padded) span of bytes, using an 92 | * unsigned integer at the start of the data or a separate {@link 93 | * module:Layout.unionLayoutDiscriminator|layout element} to 94 | * determine which layout to use when interpreting the buffer 95 | * contents; 96 | * * {@link module:Layout.bits|BitStructure}s that contain a sequence 97 | * of individual {@link 98 | * module:Layout~BitStructure#addField|BitField}s packed into an 8, 99 | * 16, 24, or 32-bit unsigned integer starting at the least- or 100 | * most-significant bit; 101 | * * {@link module:Layout.cstr|C strings} of varying length; 102 | * * {@link module:Layout.blob|Blobs} of fixed- or variable-{@link 103 | * module:Layout~Blob#length|length} raw data. 104 | * 105 | * All {@link module:Layout~Layout|Layout} instances are immutable 106 | * after construction, to prevent internal state from becoming 107 | * inconsistent. 108 | * 109 | * @local Layout 110 | * @local ExternalLayout 111 | * @local GreedyCount 112 | * @local OffsetLayout 113 | * @local UInt 114 | * @local UIntBE 115 | * @local Int 116 | * @local IntBE 117 | * @local NearUInt64 118 | * @local NearUInt64BE 119 | * @local NearInt64 120 | * @local NearInt64BE 121 | * @local Float 122 | * @local FloatBE 123 | * @local Double 124 | * @local DoubleBE 125 | * @local Sequence 126 | * @local Structure 127 | * @local UnionDiscriminator 128 | * @local UnionLayoutDiscriminator 129 | * @local Union 130 | * @local VariantLayout 131 | * @local BitStructure 132 | * @local BitField 133 | * @local Boolean 134 | * @local Blob 135 | * @local CString 136 | * @local Constant 137 | * @local bindConstructorLayout 138 | * @module Layout 139 | * @license MIT 140 | * @author Peter A. Bigot 141 | * @see {@link https://github.com/pabigot/buffer-layout|buffer-layout on GitHub} 142 | */ 143 | 144 | /** 145 | * [js-sha3]{@link https://github.com/emn178/js-sha3} 146 | * 147 | * @version 0.8.0 148 | * @author Chen, Yi-Cyuan [emn178@gmail.com] 149 | * @copyright Chen, Yi-Cyuan 2015-2018 150 | * @license MIT 151 | */ 152 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: "./src/index.ts", 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.(ts|js)?$/, 9 | exclude: /node_modules/, 10 | use: { 11 | loader: "babel-loader", 12 | options: { 13 | presets: ["@babel/preset-env", "@babel/preset-typescript"], 14 | }, 15 | }, 16 | }, 17 | ], 18 | }, 19 | resolve: { 20 | extensions: [".ts", ".js"], 21 | }, 22 | output: { 23 | filename: "wallet.js", 24 | path: path.resolve(__dirname, 'web'), 25 | library: { 26 | name: "wallet", 27 | type: "this", 28 | }, 29 | } 30 | } 31 | --------------------------------------------------------------------------------