-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.dart
355 lines (313 loc) · 11.6 KB
/
main.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
import 'package:flutter/material.dart';
import 'package:solana_wallet_provider/solana_wallet_provider.dart';
void main() {
runApp(const MaterialApp(
home: ExampleApp()),
);
}
class ExampleApp extends StatefulWidget {
const ExampleApp({
super.key,
});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
/// Request status.
String? _status;
@override
Widget build(final BuildContext context) {
// 1. Wrap application with SolanaWalletProvider.
return SolanaWalletProvider.create(
httpCluster: Cluster.devnet,
identity: AppIdentity(
uri: Uri.parse('https://my_dapp.com'),
icon: Uri.parse('favicon.png'),
name: 'My Dapp'
),
child: MaterialApp(
home: Scaffold(
body: FutureBuilder(
// 2. Initialize SolanaWalletProvider before use.
future: SolanaWalletProvider.initialize(),
builder: ((context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const CircularProgressIndicator();
}
// 3. Access SolanaWalletProvider.
final provider = SolanaWalletProvider.of(context);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Wallet Button'),
SolanaWalletButton(),
const Divider(),
const Text('Wallet Methods'),
Wrap(
spacing: 24.0,
children: [
_textButton(
'Connect',
enabled: !provider.adapter.isAuthorized,
onPressed: () => _connect(context, provider),
),
_textButton(
'Disconnect',
enabled: provider.adapter.isAuthorized,
onPressed: () => _disconnect(context, provider),
),
_textButton(
'Sign Transactions (1)',
enabled: provider.adapter.isAuthorized,
onPressed: () => _signTransactions(context, provider, 1),
),
_textButton(
'Sign Transactions (3)',
enabled: provider.adapter.isAuthorized,
onPressed: () => _signTransactions(context, provider, 3),
),
_textButton(
'Sign And Send Transactions (1)',
enabled: provider.adapter.isAuthorized,
onPressed: () => _signAndSendTransactions(context, provider, 1),
),
_textButton(
'Sign And Send Transactions (3)',
enabled: provider.adapter.isAuthorized,
onPressed: () => _signAndSendTransactions(context, provider, 3),
),
_textButton(
'Sign Messages (1)',
enabled: provider.adapter.isAuthorized,
onPressed: () => _signMessages(context, provider, 1),
),
_textButton(
'Sign Messages (3)',
enabled: provider.adapter.isAuthorized,
onPressed: () => _signMessages(context, provider, 3),
),
],
),
const Divider(),
const Text('Output'),
Text(_status ?? '-'),
],
);
}),
),
),
),
);
}
/// Connects the application to a wallet running on the device.
Future<void> _connect(
final BuildContext context,
final SolanaWalletProvider provider,
) async {
if (!provider.adapter.isAuthorized) {
await provider.connect(context);
setState(() {});
}
}
/// Disconnects the application from a wallet running on the device.
Future<void> _disconnect(
final BuildContext context,
final SolanaWalletProvider provider,
) async {
if (provider.adapter.isAuthorized) {
await provider.disconnect(context);
setState(() {});
}
}
/// Signs [count] number of transactions (then sends them to the network for processing and
/// confirms the transaction results).
void _signTransactions(
final BuildContext context,
final SolanaWalletProvider provider,
final int count,
) async {
final String description = "Sign Transactions ($count)";
try {
setState(() => _status = "Create $description...");
final List<TransferData> transfers = await _createTransfers(
provider.connection, provider.adapter, count: count,
);
setState(() => _status = "$description...");
final SignTransactionsResult result = await provider.signTransactions(
context,
transactions: transfers.map((transfer) => transfer.transaction).toList(),
);
setState(() => _status = "Broadcast $description...");
final List<String?> signatures = await provider.connection.sendSignedTransactions(
result.signedPayloads,
eagerError: true,
);
setState(() => _status = "Confirm $description...");
await _confirmTransfers(
provider.connection,
signatures: signatures.map((e) => base58To64Encode(e!)).toList(),
transfers: transfers,
);
} catch (error, stack) {
print('$description Error: $error');
print('$description Stack: $stack');
setState(() => _status = error.toString());
}
}
/// Signs and send [count] number of transactions to the network (then confirms the transaction
/// results).
void _signAndSendTransactions(
final BuildContext context,
final SolanaWalletProvider provider,
final int count,
) async {
final String description = "Sign And Send Transactions ($count)";
try {
setState(() => _status = "Create $description...");
final List<TransferData> transfers = await _createTransfers(
provider.connection, provider.adapter, count: count,
);
setState(() => _status = "$description...");
final SignAndSendTransactionsResult result = await provider.signAndSendTransactions(
context,
transactions: transfers.map((transfer) => transfer.transaction).toList(),
);
setState(() => _status = "Confirm $description...");
await _confirmTransfers(
provider.connection,
signatures: result.signatures,
transfers: transfers,
);
} catch (error, stack) {
print('$description Error: $error');
print('$description Stack: $stack');
setState(() => _status = error.toString());
}
}
void _signMessages(
final BuildContext context,
final SolanaWalletProvider provider,
final int count,
) async {
final String description = "Sign Messages ($count)";
try {
setState(() => _status = "Create $description...");
final List<String> messages = List.generate(
count,
(index) => 'Sign message $index'
);
setState(() => _status = "$description...");
final SignMessagesResult result = await provider.signMessages(
context,
messages: messages,
addresses: [provider.adapter.encodeAccount(provider.adapter.connectedAccount!)],
);
setState(() => _status = "Signed Messages ${result.signedPayloads.join('\n')}");
} catch (error, stack) {
print('$description Error: $error');
print('$description Stack: $stack');
setState(() => _status = error.toString());
}
}
/// Requests an airdrop of 2 SOL for [wallet].
Future<void> _airdrop(final Connection connection, final Pubkey wallet) async {
if (connection.httpCluster != Cluster.mainnet) {
setState(() => _status = "Requesting airdrop...");
await connection.requestAndConfirmAirdrop(wallet, solToLamports(2).toInt());
}
}
/// Creates [count] number of SOL transfer transactions.
Future<List<TransferData>> _createTransfers(
final Connection connection,
final SolanaWalletAdapter adapter, {
required final int count,
}) async {
// Check connected wallet.
setState(() => _status = "Pending...");
final Pubkey? wallet = Pubkey.tryFromBase64(adapter.connectedAccount?.address);
if (wallet == null) {
throw 'Wallet not connected';
}
// Airdrop some SOL to the wallet account if required.
setState(() => _status = "Checking balance...");
final int balance = await connection.getBalance(wallet);
if (balance < lamportsPerSol) await _airdrop(connection, wallet);
// Create a SystemProgram instruction to transfer some SOL.
setState(() => _status = "Creating transaction...");
final latestBlockhash = await connection.getLatestBlockhash();
final List<TransferData> txs = [];
for (int i = 0; i < count; ++i) {
final Keypair receiver = Keypair.generateSync();
final BigInt lamports = solToLamports(0.1);
final Transaction transaction = Transaction.v0(
payer: wallet,
recentBlockhash: latestBlockhash.blockhash,
instructions: [
SystemProgram.transfer(
fromPubkey: wallet,
toPubkey: receiver.pubkey,
lamports: lamports,
)
]
);
txs.add(TransferData(
transaction: transaction,
receiver: receiver,
lamports: lamports,
));
}
return txs;
}
/// Checks the results of [transfers].
Future<void> _confirmTransfers(
final Connection connection, {
required final List<String?> signatures,
required final List<TransferData> transfers,
}) async {
// Wait for confirmations (**You need to convert the base-64 signatures to base-58!**).
setState(() => _status = "Confirming transaction signature...");
await Future.wait(
[for (final sig in signatures) connection.confirmTransaction(base58To64Decode(sig!))],
eagerError: true,
);
// Get the receiver balances.
setState(() => _status = "Checking balance...");
final List<int> receiverBalances = await Future.wait(
[for (final transfer in transfers) connection.getBalance(transfer.receiver.pubkey)],
eagerError: true,
);
// Check the updated balances.
final List<String> results = [];
for (int i = 0; i < receiverBalances.length; ++i) {
final TransferData transfer = transfers[i];
final Pubkey pubkey = transfer.receiver.pubkey;
final BigInt balance = receiverBalances[i].toBigInt();
if (balance != transfer.lamports) throw Exception('Post transaction balance mismatch.');
results.add("Transfer: Address $pubkey received $balance SOL");
}
// Output the result.
setState(() => _status = "Success!\n\n"
"Signatures: $signatures\n\n"
"${results.join('\n')}"
"\n"
);
}
TextButton _textButton(
final String text, {
required final bool enabled,
required final void Function() onPressed,
}) => TextButton(
onPressed: enabled ? onPressed : null,
child: Text(text),
);
}
class TransferData {
const TransferData({
required this.transaction,
required this.receiver,
required this.lamports,
});
final Transaction transaction;
final Keypair receiver;
final BigInt lamports;
}