Line data Source code
1 : import 'dart:async';
2 : import 'dart:convert';
3 :
4 : import 'package:walletkit_dart/src/common/http_client.dart';
5 : import 'package:walletkit_dart/walletkit_dart.dart';
6 :
7 : const _openchainEndpoint = "https://api.openchain.xyz/signature-database/v1";
8 : const _4byteEndpoint = "https://www.4byte.directory/api/v1";
9 :
10 : class FunctionSelectorRepository {
11 : final Map<String, Completer<ExternalContractFunction?>> functionCache = {};
12 :
13 2 : static final FunctionSelectorRepository _instance =
14 1 : FunctionSelectorRepository._();
15 :
16 1 : FunctionSelectorRepository._();
17 :
18 1 : factory FunctionSelectorRepository() {
19 1 : return _instance;
20 : }
21 :
22 1 : Future<ExternalContractFunction?> fetchSelector(
23 : String selector, {
24 : bool openChain = true,
25 : bool fourByte = true,
26 : }) async {
27 1 : assert(openChain || fourByte, "At least one source must be enabled");
28 :
29 : if (fourByte) {
30 1 : final fourByteFunction = await fetchSelector4Byte(selector);
31 : if (fourByteFunction != null) return fourByteFunction;
32 : }
33 :
34 : if (openChain) {
35 1 : final openChainFunction = await fetchSelectorOpenChain(selector);
36 : if (openChainFunction != null) return openChainFunction;
37 : }
38 :
39 : return null;
40 : }
41 :
42 1 : Future<ExternalContractFunction?> fetchSelector4Byte(
43 : String selector,
44 : ) async {
45 2 : selector = selector.startsWith("0x") ? selector : "0x$selector";
46 :
47 1 : final endpoint = "$_4byteEndpoint/signatures/?hex_signature=$selector";
48 1 : final uri = Uri.parse(endpoint);
49 :
50 2 : if (functionCache.containsKey(endpoint)) {
51 3 : return await functionCache[endpoint]!.future;
52 : }
53 :
54 1 : final future = _4byteRequest(uri, selector);
55 :
56 1 : final completer = Completer<ExternalContractFunction?>();
57 2 : functionCache[endpoint] = completer;
58 :
59 : try {
60 : final function = await future;
61 1 : completer.complete(function);
62 : return function;
63 : } catch (e) {
64 0 : completer.complete(null);
65 : return null;
66 : }
67 : }
68 :
69 1 : Future<ExternalContractFunction?> fetchSelectorOpenChain(
70 : String selector,
71 : ) async {
72 2 : selector = selector.startsWith("0x") ? selector : "0x$selector";
73 :
74 : final endpoint =
75 1 : "$_openchainEndpoint/lookup?function=$selector&filter=true";
76 1 : final uri = Uri.parse(endpoint);
77 :
78 2 : if (functionCache.containsKey(endpoint)) {
79 3 : return await functionCache[endpoint]!.future;
80 : }
81 :
82 1 : final future = _openchainRequest(uri, selector);
83 :
84 1 : final completer = Completer<ExternalContractFunction?>();
85 2 : functionCache[endpoint] = completer;
86 : try {
87 : final function = await future;
88 1 : completer.complete(function);
89 : return function;
90 : } catch (e) {
91 0 : completer.complete(null);
92 : return null;
93 : }
94 : }
95 :
96 1 : Future<ExternalContractFunction?> _openchainRequest(
97 : Uri uri,
98 : String selector,
99 : ) async {
100 2 : final response = await HTTPService.client.get(uri);
101 :
102 2 : if (response.statusCode == 200) {
103 2 : final json = jsonDecode(response.body);
104 :
105 : if (json
106 1 : case {
107 2 : "ok": true,
108 2 : "result": {
109 2 : "event": Json _,
110 2 : "function": Json function_json,
111 : }
112 : }) {
113 1 : final functions = function_json[selector] as List<dynamic>?;
114 :
115 1 : if (functions == null || functions.isEmpty) return null;
116 :
117 2 : final name = functions.first["name"] as String?;
118 :
119 : if (name == null) return null;
120 :
121 : final function =
122 1 : ContractFunction.fromTextSignature(textSignature: name);
123 :
124 : if (function == null) return null;
125 :
126 : return function;
127 : }
128 :
129 : return null;
130 : }
131 :
132 : return null;
133 : }
134 :
135 1 : Future<ExternalContractFunction?> _4byteRequest(
136 : Uri uri,
137 : String selector,
138 : ) async {
139 2 : final response = await HTTPService.client.get(uri);
140 2 : if (response.statusCode == 200) {
141 2 : final contentType = response.headers["content-type"];
142 :
143 1 : if (contentType != null && !contentType.contains("application/json")) {
144 : return null;
145 : }
146 :
147 2 : final responseData = jsonDecode(response.body);
148 :
149 : // TODO: Maybe return a List of all Functions so that we can display all possible functions
150 2 : final function = getLowestIdFunction(responseData["results"]);
151 :
152 : return function;
153 : }
154 :
155 : return null;
156 : }
157 :
158 1 : static ExternalContractFunction? getLowestIdFunction(List<dynamic> results) {
159 5 : results.sort((a, b) => a["id"].compareTo(b["id"]));
160 :
161 1 : for (final {
162 2 : "id": int _,
163 1 : "created_at": _,
164 2 : "text_signature": String text_signature,
165 1 : "hex_signature": _,
166 1 : "bytes_signature": _,
167 2 : } in results) {
168 1 : return ContractFunction.fromTextSignature(
169 : textSignature: text_signature,
170 : );
171 : }
172 : return null;
173 : }
174 : }
|