diff --git a/lib/utilities/extensions/impl/contract_abi.dart b/lib/utilities/extensions/impl/contract_abi.dart new file mode 100644 index 000000000..c5a2877d8 --- /dev/null +++ b/lib/utilities/extensions/impl/contract_abi.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; + +import 'package:web3dart/web3dart.dart'; + +extension ContractAbiExtensions on ContractAbi { + static ContractAbi fromJsonList({ + required String name, + required String jsonList, + }) { + final List functions = []; + final List events = []; + + final list = List>.from(jsonDecode(jsonList) as List); + + for (final json in list) { + final type = json["type"] as String; + final name = json["name"] as String? ?? ""; + + if (type == "event") { + final anonymous = json["anonymous"] as bool? ?? false; + final List> components = []; + + for (final input in json["inputs"] as List) { + components.add( + EventComponent( + _parseParam(input as Map), + input['indexed'] as bool? ?? false, + ), + ); + } + + events.add(ContractEvent(anonymous, name, components)); + } else { + final mutability = _mutabilityNames[json['stateMutability']]; + final parsedType = _functionTypeNames[json['type']]; + if (parsedType != null) { + final inputs = _parseParams(json['inputs'] as List?); + final outputs = _parseParams(json['outputs'] as List?); + + functions.add( + ContractFunction( + name, + inputs, + outputs: outputs, + type: parsedType, + mutability: mutability ?? StateMutability.nonPayable, + ), + ); + } + } + } + + return ContractAbi(name, functions, events); + } + + static const Map _functionTypeNames = { + 'function': ContractFunctionType.function, + 'constructor': ContractFunctionType.constructor, + 'fallback': ContractFunctionType.fallback, + }; + + static const Map _mutabilityNames = { + 'pure': StateMutability.pure, + 'view': StateMutability.view, + 'nonpayable': StateMutability.nonPayable, + 'payable': StateMutability.payable, + }; + + static List> _parseParams(List? data) { + if (data == null || data.isEmpty) return []; + + final elements = >[]; + for (final entry in data) { + elements.add(_parseParam(entry as Map)); + } + + return elements; + } + + static FunctionParameter _parseParam(Map entry) { + final name = entry['name'] as String; + final typeName = entry['type'] as String; + + if (typeName.contains('tuple')) { + final components = entry['components'] as List; + return _parseTuple(name, typeName, _parseParams(components)); + } else { + final type = parseAbiType(entry['type'] as String); + return FunctionParameter(name, type); + } + } + + static CompositeFunctionParameter _parseTuple(String name, String typeName, + List> components) { + // The type will have the form tuple[3][]...[1], where the indices after the + // tuple indicate that the type is part of an array. + assert(RegExp(r'^tuple(?:\[\d*\])*$').hasMatch(typeName), + '$typeName is an invalid tuple type'); + + final arrayLengths = []; + var remainingName = typeName; + + while (remainingName != 'tuple') { + final arrayMatch = RegExp(r'^(.*)\[(\d*)\]$').firstMatch(remainingName)!; + remainingName = arrayMatch.group(1)!; + + final insideSquareBrackets = arrayMatch.group(2)!; + if (insideSquareBrackets.isEmpty) { + arrayLengths.insert(0, null); + } else { + arrayLengths.insert(0, int.parse(insideSquareBrackets)); + } + } + + return CompositeFunctionParameter(name, components, arrayLengths); + } +}