Line data Source code
1 : import 'dart:async';
2 : import 'dart:typed_data';
3 : import 'package:walletkit_dart/src/common/logger.dart';
4 : import 'package:walletkit_dart/src/crypto/evm/entities/block_number.dart';
5 : import 'package:walletkit_dart/src/crypto/evm/repositories/rpc/queued_rpc_interface.dart';
6 : import 'package:walletkit_dart/src/domain/exceptions.dart';
7 : import 'package:walletkit_dart/src/utils/int.dart';
8 : import 'package:walletkit_dart/walletkit_dart.dart';
9 :
10 : const type2Multiplier = 1.5;
11 :
12 : final class EvmRpcInterface {
13 : final EVMNetworkType type;
14 : final Map<int, int> blockTimestampCache = {};
15 : final Map<String, ConfirmationStatus> txStatusCache = {};
16 : final RpcManager _manager;
17 :
18 0 : Future<void> get refreshFuture => _manager.refreshFuture;
19 :
20 : ///
21 : /// [clients] - A list of clients to use for the manager
22 : /// [useQueuedManager] - If true, the manager will use a QueuedRpcManager and requests will be queued
23 : /// [awaitRefresh] - If true, the manager will wait for the clients to be refreshed before performing a task
24 : /// [refreshIntervall] - The rate at which the clients are refreshed if null the clients will only be refreshed once
25 : /// [eagerError] - If true a task will throw the first error it encounters, if false it will try all clients before throwing an error
26 : ///
27 5 : EvmRpcInterface({
28 : bool useQueuedManager = true,
29 : bool awaitRefresh = true,
30 : Duration? refreshIntervall,
31 : bool eagerError = false,
32 : RefreshType refreshType = RefreshType.onTask,
33 : required List<EvmRpcClient> clients,
34 : required this.type,
35 : }) : _manager = useQueuedManager
36 4 : ? QueuedRpcManager(
37 : awaitRefresh: awaitRefresh,
38 : clientRefreshRate: refreshIntervall,
39 : allClients: clients,
40 : eagerError: eagerError,
41 : refreshType: refreshType,
42 : )
43 3 : : SimpleRpcManager(
44 : awaitRefresh: awaitRefresh,
45 : clientRefreshRate: refreshIntervall,
46 : allClients: clients,
47 : eagerError: eagerError,
48 : refreshType: refreshType,
49 : );
50 :
51 5 : Future<T> performTask<T>(
52 : Future<T> Function(EvmRpcClient client) task, {
53 : Duration timeout = const Duration(seconds: 30),
54 : int? maxTries,
55 : }) =>
56 15 : _manager.performTask(task, timeout: timeout, maxTries: maxTries).then(
57 5 : (valueOrError) {
58 5 : return valueOrError.when(
59 10 : value: (value) => value.value,
60 0 : error: (error) => throw Exception(error),
61 : );
62 : },
63 : );
64 :
65 0 : Future<R> performTaskForClients<T, R>(
66 : Future<T> Function(EvmRpcClient) task, {
67 : required R Function(
68 : List<ValueOrError<T, EvmRpcClient>> results,
69 : ) consilidate,
70 : Duration timeout = const Duration(seconds: 30),
71 : int maxTriesPerClient = 2,
72 : int minClients = 2,
73 : int? maxClients,
74 : bool enforceParallel = false,
75 : }) =>
76 0 : _manager.performTaskForClients(
77 : task,
78 : consilidate: consilidate,
79 : timeout: timeout,
80 : maxTriesPerClient: maxTriesPerClient,
81 : maxClients: maxClients,
82 : minClients: minClients,
83 : enforceParallel: enforceParallel,
84 : );
85 :
86 : ///
87 : /// eth_call
88 : ///
89 3 : Future<String> call({
90 : String? sender,
91 : required String contractAddress,
92 : required Uint8List data,
93 : BlockNum? atBlock,
94 : }) {
95 3 : return performTask(
96 6 : (client) => client.call(
97 : sender: sender,
98 : contractAddress: contractAddress,
99 : data: data,
100 : atBlock: atBlock,
101 : ),
102 : );
103 : }
104 :
105 : ///
106 : /// Fetch Balance
107 : ///
108 2 : Future<Amount> fetchBalance({
109 : required String address,
110 : }) async {
111 6 : final balance = await performTask((client) => client.getBalance(address));
112 2 : return Amount(
113 : value: balance,
114 6 : decimals: type.coin.decimals,
115 : );
116 : }
117 :
118 : ///
119 : /// Fetch Token Balance
120 : ///
121 1 : Future<Amount> fetchTokenBalance(
122 : String address,
123 : ERC20Entity token,
124 : ) async {
125 1 : final erc20Contract = ERC20Contract(
126 1 : contractAddress: token.contractAddress,
127 : rpc: this,
128 : );
129 1 : final balance = await erc20Contract.getBalance(address);
130 2 : return Amount(value: balance, decimals: token.decimals);
131 : }
132 :
133 : ///
134 : /// Fetch Balance of ERC1155 Token
135 : ///
136 1 : Future<Amount> fetchERC1155BalanceOfToken({
137 : required String address,
138 : required BigInt tokenID,
139 : required String contractAddress,
140 : }) async {
141 1 : final erc1155Contract = ERC1155Contract(
142 : contractAddress: contractAddress,
143 : rpc: this,
144 : );
145 1 : final balance = await erc1155Contract.balanceOf(
146 : address: address,
147 : tokenID: tokenID,
148 : );
149 :
150 1 : return Amount(value: balance, decimals: 0);
151 : }
152 :
153 : ///
154 : /// Fetch Batch Balance of ERC1155 Tokens
155 : ///
156 1 : Future<List<BigInt>> fetchERC1155BatchBalanceOfTokens({
157 : required List<String> accounts,
158 : required List<BigInt> tokenIDs,
159 : required String contractAddress,
160 : }) async {
161 1 : final erc1155Contract = ERC1155Contract(
162 : contractAddress: contractAddress,
163 : rpc: this,
164 : );
165 :
166 1 : final balances = await erc1155Contract.balanceOfBatch(
167 : accounts: accounts,
168 : tokenIDs: tokenIDs,
169 : );
170 :
171 : return balances;
172 : }
173 :
174 : ///
175 : /// Fetch Uri of ERC115 Token
176 : ///
177 1 : Future<String> fetchERC1155UriOfToken({
178 : required BigInt tokenID,
179 : required String contractAddress,
180 : }) async {
181 1 : final erc1155Contract = ERC1155Contract(
182 : contractAddress: contractAddress,
183 : rpc: this,
184 : );
185 :
186 1 : final uri = await erc1155Contract.getUri(
187 : tokenID: tokenID,
188 : );
189 :
190 : return uri;
191 : }
192 :
193 1 : Future<(Amount, int)> estimateNetworkFees({
194 : required String recipient,
195 : required String sender,
196 : required Uint8List? data,
197 : required BigInt? value,
198 : }) async {
199 1 : final gasPrice = await getGasPrice();
200 1 : final gasLimit = await estimateGasLimit(
201 : recipient: recipient,
202 : sender: sender,
203 : data: data,
204 : value: value,
205 : gasPrice: gasPrice,
206 : );
207 :
208 1 : return (Amount(value: gasPrice, decimals: 18), gasLimit);
209 : }
210 :
211 : ///
212 : /// Get Gas Price
213 : ///
214 1 : Future<BigInt> getGasPrice() async {
215 1 : return await performTask(
216 2 : (client) => client.getGasPrice(),
217 : );
218 : }
219 :
220 : ///
221 : /// Get Gas Price
222 : ///
223 3 : Future<Amount> getGasPriceAmount() => getGasPrice().then(
224 2 : (value) => Amount(value: value, decimals: 18),
225 : );
226 :
227 : ///
228 : /// Get Transaction Count (Nonce)
229 : ///
230 0 : Future<BigInt> getTransactionCount(String address) async {
231 0 : return await performTask(
232 0 : (client) => client.getTransactionCount(address),
233 : );
234 : }
235 :
236 : ///
237 : /// Get Transaction By Hash
238 : ///
239 1 : Future<RawEvmTransaction> getTransactionByHash(String hash) async {
240 1 : return await performTask(
241 2 : (client) => client.getTransactionByHash(hash),
242 : );
243 : }
244 :
245 : ///
246 : /// Send Currency
247 : ///
248 0 : Future<String> sendCoin({
249 : required TransferIntent<EvmFeeInformation> intent,
250 : required String from,
251 : required Uint8List privateKey,
252 : }) async {
253 0 : final tx = await buildTransaction(
254 : sender: from,
255 0 : recipient: intent.recipient,
256 : privateKey: privateKey,
257 0 : feeInfo: intent.feeInfo,
258 0 : data: intent.encodedMemo,
259 0 : value: intent.amount.value,
260 0 : accessList: intent.accessList,
261 : );
262 0 : final balance = await fetchBalance(address: toChecksumAddress(from)).then(
263 0 : (amount) => amount.value,
264 : );
265 :
266 0 : if (balance < tx.gasFee + tx.value) {
267 0 : throw WKFailure("Insufficient funds to pay native gas fee");
268 : }
269 :
270 0 : return await sendRawTransaction(tx.serialized.toHex);
271 : }
272 :
273 : ///
274 : /// Send ERC20 Token
275 : ///
276 0 : Future<String> sendERC20Token({
277 : required TransferIntent<EvmFeeInformation> intent,
278 : required String from,
279 : required Uint8List privateKey,
280 : }) async {
281 0 : assert(intent.token is ERC20Entity);
282 0 : assert(intent.memo == null);
283 :
284 0 : final erc20 = intent.token as ERC20Entity;
285 0 : final tokenContractAddress = erc20.contractAddress;
286 :
287 0 : final erc20Contract = ERC20Contract(
288 : contractAddress: tokenContractAddress,
289 : rpc: this,
290 : );
291 :
292 0 : return erc20Contract.transfer(
293 : privateKey: privateKey,
294 : sender: from,
295 0 : to: intent.recipient,
296 0 : value: intent.amount.value,
297 0 : feeInfo: intent.feeInfo,
298 0 : accessList: intent.accessList,
299 : );
300 : }
301 :
302 : ///
303 : /// Send ERC1155 Token
304 : ///
305 0 : Future<String> sendERC1155Token({
306 : required TransferIntent<EvmFeeInformation> intent,
307 : required String contractAddress,
308 : required BigInt tokenID,
309 : required String from,
310 : required Uint8List privateKey,
311 : }) async {
312 0 : final erc1155Contract = ERC1155Contract(
313 : contractAddress: contractAddress,
314 : rpc: this,
315 : );
316 :
317 0 : return erc1155Contract.safeTransferFrom(
318 : sender: from,
319 0 : to: intent.recipient,
320 : tokenID: tokenID,
321 0 : amount: intent.amount.value,
322 : privateKey: privateKey,
323 0 : feeInfo: intent.feeInfo,
324 0 : accessList: intent.accessList,
325 : );
326 : }
327 :
328 1 : Future<Amount> getPriorityFee() async {
329 1 : final priorityFee = await performTask(
330 2 : (client) => client.getPriorityFee(),
331 : );
332 :
333 1 : return Amount(value: priorityFee, decimals: 9);
334 : }
335 :
336 1 : Future<EvmType2GasPrice> getType2GasPrice() async {
337 1 : final maxFeePerGas = await getGasPriceAmount();
338 1 : final maxPriorityFeePerGas = await getPriorityFee();
339 :
340 1 : return EvmType2GasPrice(
341 : maxFeePerGas:
342 2 : maxFeePerGas.multiplyAndCeil(type2Multiplier) + maxPriorityFeePerGas,
343 : maxPriorityFeePerGas: maxPriorityFeePerGas,
344 : );
345 : }
346 :
347 1 : Future<(int gasLimit, EvmGasPrice gasPrice)> fetchNetworkFees({
348 : EvmFeeInformation? existing,
349 : required String recipient,
350 : required String sender,
351 : required Uint8List? data,
352 : required BigInt? value,
353 : }) async {
354 0 : var gasLimit = existing?.gasLimit;
355 : try {
356 1 : gasLimit ??= await estimateGasLimit(
357 : recipient: recipient,
358 : sender: sender,
359 : data: data,
360 : value: value,
361 : );
362 : } catch (e) {
363 0 : Logger.logError(e, hint: "Gas estimation failed");
364 :
365 : // Only Debug
366 0 : assert(true, "Gas estimation failed");
367 :
368 0 : gasLimit = 1E6.toInt();
369 : }
370 :
371 0 : final EvmGasPrice gasPrice = switch (existing?.gasPrice) {
372 1 : EvmLegacyGasPrice feeInfo => feeInfo,
373 1 : EvmType2GasPrice feeInfo => feeInfo,
374 3 : null when type.useEIP1559 => await getType2GasPrice(),
375 0 : null => EvmLegacyGasPrice(
376 0 : gasPrice: await getGasPriceAmount(),
377 : ),
378 : };
379 :
380 : return (gasLimit, gasPrice);
381 : }
382 :
383 : ///
384 : /// Used to create a raw Transactions
385 : /// Fetches the gasPrice and gasLimit from the network
386 : /// Fetches the nonce from the network
387 : /// If Transaction Type is not provided, it will use Legacy
388 : ///
389 0 : Future<RawEvmTransaction> buildUnsignedTransaction({
390 : required String sender,
391 : required String recipient,
392 : required EvmFeeInformation? feeInfo,
393 : required Uint8List? data,
394 : required BigInt? value,
395 : List<AccessListItem>? accessList,
396 : }) async {
397 0 : final (gasLimit, gasPrice) = await fetchNetworkFees(
398 : recipient: recipient,
399 : sender: sender,
400 : data: data,
401 : value: value,
402 : existing: feeInfo,
403 : );
404 :
405 0 : final nonce = await performTask(
406 0 : (client) => client.getTransactionCount(sender),
407 : );
408 :
409 : return switch (gasPrice) {
410 0 : EvmType2GasPrice fee => RawEVMTransactionType2.unsigned(
411 : nonce: nonce,
412 0 : maxFeePerGas: fee.maxFeePerGas.value,
413 0 : maxPriorityFeePerGas: fee.maxPriorityFeePerGas.value,
414 0 : gasLimit: gasLimit.toBI,
415 : to: recipient,
416 0 : value: value ?? BigInt.zero,
417 0 : data: data ?? Uint8List(0),
418 0 : accessList: accessList ?? [],
419 0 : chainId: type.chainId,
420 : ),
421 0 : EvmLegacyGasPrice fee => accessList != null
422 0 : ? RawEVMTransactionType1.unsigned(
423 : nonce: nonce,
424 0 : gasPrice: fee.gasPrice.value,
425 0 : gasLimit: gasLimit.toBI,
426 : to: recipient,
427 0 : value: value ?? BigInt.zero,
428 0 : data: data ?? Uint8List(0),
429 : accessList: accessList,
430 0 : chainId: type.chainId,
431 : )
432 0 : : RawEVMTransactionType0.unsigned(
433 : nonce: nonce,
434 0 : gasPrice: fee.gasPrice.value,
435 0 : gasLimit: gasLimit.toBI,
436 : to: recipient,
437 0 : value: value ?? BigInt.zero,
438 0 : data: data ?? Uint8List(0),
439 : ),
440 : };
441 : }
442 :
443 : ///
444 : /// Used to create a raw Transactions
445 : /// Fetches the gasPrice and gasLimit from the network
446 : /// Fetches the nonce from the network
447 : /// Signs the transaction
448 : ///
449 0 : Future<RawEvmTransaction> buildTransaction({
450 : required String sender,
451 : required String recipient,
452 : required Uint8List privateKey,
453 : required EvmFeeInformation? feeInfo,
454 : required Uint8List? data,
455 : required BigInt? value,
456 : List<AccessListItem>? accessList,
457 : }) async {
458 0 : final unsignedTx = await buildUnsignedTransaction(
459 : sender: sender,
460 : recipient: recipient,
461 : feeInfo: feeInfo,
462 : data: data,
463 : value: value,
464 : accessList: accessList,
465 : );
466 :
467 0 : final signature = Signature.createSignature(
468 : switch (unsignedTx) {
469 0 : RawEVMTransactionType0() => unsignedTx.serializedUnsigned(type.chainId),
470 0 : RawEVMTransactionType1() => unsignedTx.serializedUnsigned,
471 0 : RawEVMTransactionType2() => unsignedTx.serializedUnsigned,
472 : },
473 : txType: switch (unsignedTx) {
474 0 : RawEVMTransactionType0() => TransactionType.Legacy,
475 0 : RawEVMTransactionType1() => TransactionType.Type1,
476 0 : RawEVMTransactionType2() => TransactionType.Type2,
477 : },
478 : privateKey,
479 0 : chainId: type.chainId,
480 : );
481 :
482 0 : final signedTx = unsignedTx.addSignature(signature);
483 :
484 : return signedTx;
485 : }
486 :
487 0 : Future<String> sendRawTransaction(String serializedTransactionHex) {
488 0 : serializedTransactionHex = serializedTransactionHex.startsWith("0x")
489 : ? serializedTransactionHex
490 0 : : "0x$serializedTransactionHex";
491 0 : return performTaskForClients(
492 0 : (client) => client.sendRawTransaction(serializedTransactionHex),
493 : minClients: 1,
494 : maxTriesPerClient: 1,
495 : maxClients: 5,
496 : enforceParallel: true,
497 0 : consilidate: (resultsWithErrors) {
498 : final results = resultsWithErrors
499 0 : .whereType<Value<String, EvmRpcClient>>()
500 0 : .map((v) => v.value);
501 :
502 0 : if (results.isEmpty) {
503 0 : throw Exception(
504 0 : "No client was able to send the transaction: ${results}",
505 : );
506 : }
507 :
508 0 : final hashMap = results.fold<Map<String, int>>(
509 0 : {},
510 0 : (acc, hash) {
511 0 : acc[hash] = (acc[hash] ?? 0) + 1;
512 : return acc;
513 : },
514 : );
515 :
516 0 : final hash = hashMap.entries.reduce(
517 0 : (a, b) => a.value > b.value ? a : b,
518 : );
519 :
520 0 : return hash.key;
521 : },
522 : );
523 : }
524 :
525 0 : Future<String> buildAndBroadcastTransaction({
526 : required String sender,
527 : required String recipient,
528 : required Uint8List privateKey,
529 : required EvmFeeInformation? feeInfo,
530 : required Uint8List? data,
531 : required BigInt? value,
532 : List<AccessListItem>? accessList,
533 : }) async {
534 0 : final signedTx = await buildTransaction(
535 : sender: sender,
536 : recipient: recipient,
537 : privateKey: privateKey,
538 : feeInfo: feeInfo,
539 : data: data,
540 : value: value,
541 : accessList: accessList,
542 : );
543 :
544 0 : final result = await sendRawTransaction(signedTx.serialized.toHex);
545 :
546 : return result;
547 : }
548 :
549 0 : Future<String> readContract({
550 : required String contractAddress,
551 : required LocalContractFunctionWithValues function,
552 : }) async {
553 : assert(
554 0 : function.stateMutability == StateMutability.view ||
555 0 : function.stateMutability == StateMutability.pure,
556 : "Invalid function",
557 : );
558 :
559 0 : final data = function.buildDataField();
560 :
561 0 : return await call(
562 : contractAddress: contractAddress,
563 : data: data,
564 : );
565 : }
566 :
567 : ///
568 : /// Interact with Contract
569 : ///
570 0 : Future<String> interactWithContract({
571 : required String contractAddress,
572 : required LocalContractFunctionWithValues function,
573 : required String sender,
574 : required Uint8List privateKey,
575 : required EvmFeeInformation? feeInfo,
576 : BigInt? value,
577 : }) async {
578 0 : final valid = switch ((function.stateMutability, value)) {
579 0 : (StateMutability.nonpayable, BigInt? value) => value == null ||
580 0 : value == BigInt.zero, // If nonpayable, value must be 0 or null
581 0 : (StateMutability.payable, BigInt? value) =>
582 0 : value != null && value != BigInt.zero, // If payable, value must be set
583 : _ => false,
584 : };
585 0 : assert(valid, "Invalid value for state mutability of function");
586 :
587 0 : final data = function.buildDataField();
588 :
589 0 : return await buildAndBroadcastTransaction(
590 : sender: sender,
591 : recipient: contractAddress,
592 : privateKey: privateKey,
593 : feeInfo: feeInfo,
594 : data: data,
595 0 : value: value ?? BigInt.zero,
596 : );
597 : }
598 :
599 1 : Future<int> estimateGasLimit({
600 : required String sender,
601 : required String recipient,
602 : Uint8List? data,
603 : BigInt? value,
604 : BigInt? gasPrice,
605 : }) async {
606 2 : final dataHex = data != null ? "0x${data.toHex}" : null;
607 :
608 1 : return await performTask(
609 1 : (client) => client
610 1 : .estimateGasLimit(
611 : from: sender,
612 : to: recipient,
613 : data: dataHex,
614 : amount: value,
615 : gasPrice: gasPrice,
616 : )
617 1 : .then(
618 2 : (value) => value.toInt(),
619 : ),
620 : );
621 : }
622 :
623 : ///
624 : /// Send ERC721
625 : ///
626 0 : Future<String> sendERC721Nft({
627 : required String recipient,
628 : required String from,
629 : required int tokenId,
630 : required String contractAddress,
631 : required Uint8List privateKey,
632 : }) async {
633 0 : final function = LocalContractFunctionWithValues(
634 : name: "transferFrom",
635 0 : parameters: [
636 0 : FunctionParamWithValue(
637 : name: "from",
638 0 : type: FunctionParamAddress(),
639 : value: from,
640 : ),
641 0 : FunctionParamWithValue(
642 : name: "to",
643 0 : type: FunctionParamAddress(),
644 : value: recipient,
645 : ),
646 0 : FunctionParamWithValue(
647 : name: "tokenId",
648 0 : type: FunctionParamInt(),
649 : value: tokenId,
650 : ),
651 : ],
652 : stateMutability: StateMutability.nonpayable,
653 0 : outputTypes: [],
654 : );
655 :
656 0 : return await interactWithContract(
657 : contractAddress: contractAddress,
658 : function: function,
659 : sender: from,
660 : privateKey: privateKey,
661 : feeInfo: null,
662 : );
663 : }
664 :
665 0 : Future<ConfirmationStatus> getConfirmationStatus(String hash) async {
666 0 : if (txStatusCache[hash] == null ||
667 0 : txStatusCache[hash] == ConfirmationStatus.pending) {
668 0 : final json = await performTask(
669 0 : (client) => client.getTransactionReceipt(hash),
670 : );
671 0 : txStatusCache[hash] = _confirmationStatusFromJson(json ?? {});
672 : }
673 0 : return txStatusCache[hash]!;
674 : }
675 :
676 0 : ConfirmationStatus _confirmationStatusFromJson(Json json) {
677 : if (json
678 : case {
679 0 : "status": String status_s,
680 : }) {
681 0 : final status = status_s.toBigIntOrNull;
682 0 : if (status == null) throw Exception('Could not parse status');
683 0 : if (status == BigInt.from(0)) return ConfirmationStatus.failed;
684 0 : if (status == BigInt.from(1)) return ConfirmationStatus.confirmed;
685 : }
686 :
687 : return ConfirmationStatus.pending;
688 : }
689 :
690 : ///
691 : /// Get Current Block
692 : ///
693 0 : Future<Json> getCurrentBlock() async {
694 0 : final blockNumber = await getBlockNumber();
695 0 : return await performTask(
696 0 : (client) => client.getBlockByNumber(blockNumber),
697 : );
698 : }
699 :
700 : ///
701 : /// Get Block Number
702 : ///
703 1 : Future<int> getBlockNumber() async {
704 1 : return await performTask(
705 2 : (client) => client.getBlockNumber(),
706 : );
707 : }
708 :
709 0 : Future<bool> waitForTxConfirmation(
710 : String hash, {
711 : Duration interval = const Duration(seconds: 5),
712 : }) async {
713 : while (true) {
714 0 : await Future.delayed(interval);
715 :
716 0 : final receipt = await performTask(
717 0 : (client) => client.getTransactionReceipt(hash),
718 : );
719 :
720 0 : switch (receipt?['status']) {
721 0 : case '0x1':
722 : return true;
723 0 : case '0x0':
724 : return false;
725 : default:
726 : }
727 : }
728 : }
729 :
730 1 : Future<String?> resolveENS({
731 : required String name,
732 : required String contractAddress,
733 : }) async {
734 1 : name = name.toLowerCase();
735 1 : final contract = EnsRegistryContract(
736 : rpc: this,
737 : contractAddress: contractAddress,
738 : );
739 :
740 1 : final resolverAddress = await contract.resolver(name: name);
741 :
742 : if (resolverAddress == null) {
743 : return null;
744 : }
745 :
746 1 : final resolver = EnsResolverContract(
747 : contractAddress: resolverAddress,
748 : rpc: this,
749 : );
750 :
751 1 : final addr = await resolver.addr(name: name);
752 :
753 : return addr;
754 : }
755 : }
|