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