// Path: src/main/java/org/adridadou/ethereum/converters/future/CompletableFutureConverter.java
// public class CompletableFutureConverter implements FutureConverter {
//     @Override
//     public CompletableFuture convert(CompletableFuture future) {
//         return future;
//     }
// 
//     @Override
//     public boolean isFutureType(Class cls) {
//         return CompletableFuture.class.equals(cls);
//     }
// 
//     @Override
//     public boolean isPayableType(Class cls) {
//         return Payable.class.equals(cls);
//     }
// 
//     @Override
//     public Payable getPayable(SmartContract smartContract, String methodName, Object[] arguments, Method method, EthereumContractInvocationHandler ethereumContractInvocationHandler) {
//         return new Payable(smartContract,methodName,arguments, method, ethereumContractInvocationHandler);
//     }
// }
// 
// Path: src/main/java/org/adridadou/ethereum/values/EthValue.java
// public static EthValue wei(final int value) {
//     return wei(BigInteger.valueOf(value));
// }


import com.google.common.collect.Sets;
import org.adridadou.ethereum.converters.future.*;
import org.adridadou.ethereum.converters.future.CompletableFutureConverter;
import org.adridadou.ethereum.converters.input.*;
import org.adridadou.ethereum.converters.output.*;
import org.adridadou.ethereum.values.*;
import org.adridadou.exception.EthereumApiException;
import org.ethereum.core.CallTransaction;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.adridadou.ethereum.values.EthValue.wei;
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        final String methodName = method.getName();
        SmartContractInfo contractInfo = info.get(new ProxyWrapper(proxy));
        SmartContract contract = contracts.get(contractInfo.getAddress()).get(contractInfo.getAccount());
        Object[] arguments = Optional.ofNullable(args).map(this::prepareArguments).orElse(new Object[0]);

        if (method.getReturnType().equals(Void.TYPE)) {
            try {
                contract.callFunction(methodName, arguments).get();
            }
            catch (ExecutionException e) {
                throw e.getCause();
            }

            return Void.TYPE;
        } else {
            return findConverter(method.getReturnType()).map(converter -> {
                if (converter.isFutureType(method.getReturnType())) {
                    return converter.convert(contract.callFunction(methodName, arguments).thenApply(result -> convertResult(result, method)));
                }
                return converter.getPayable(contract, methodName, arguments, method, this);
            }).orElseGet(() -> convertResult(contract.callConstFunction(methodName, wei(0), arguments), method));
        }
    }

    private Optional<FutureConverter> findConverter(Class type) {
        return futureConverters.stream().filter(converter -> converter.isFutureType(type) || converter.isPayableType(type)).findFirst();
    }

// Path: src/main/java/org/adridadou/ethereum/converters/future/CompletableFutureConverter.java
// public class CompletableFutureConverter implements FutureConverter {
//     @Override
//     public CompletableFuture convert(CompletableFuture future) {
//         return future;
//     }
// 
//     @Override
//     public boolean isFutureType(Class cls) {
//         return CompletableFuture.class.equals(cls);
//     }
// 
//     @Override
//     public boolean isPayableType(Class cls) {
//         return Payable.class.equals(cls);
//     }
// 
//     @Override
//     public Payable getPayable(SmartContract smartContract, String methodName, Object[] arguments, Method method, EthereumContractInvocationHandler ethereumContractInvocationHandler) {
//         return new Payable(smartContract,methodName,arguments, method, ethereumContractInvocationHandler);
//     }
// }
// 
// Path: src/main/java/org/adridadou/ethereum/values/EthValue.java
// public static EthValue wei(final int value) {
//     return wei(BigInteger.valueOf(value));
// }


// Path: src/main/java/org/adridadou/ethereum/EthereumContractInvocationHandler.java
import com.google.common.collect.Sets;
import org.adridadou.ethereum.converters.future.*;
import org.adridadou.ethereum.converters.future.CompletableFutureConverter;
import org.adridadou.ethereum.converters.input.*;
import org.adridadou.ethereum.converters.output.*;
import org.adridadou.ethereum.values.*;
import org.adridadou.exception.EthereumApiException;
import org.ethereum.core.CallTransaction;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.adridadou.ethereum.values.EthValue.wei;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        final String methodName = method.getName();
        SmartContractInfo contractInfo = info.get(new ProxyWrapper(proxy));
        SmartContract contract = contracts.get(contractInfo.getAddress()).get(contractInfo.getAccount());
        Object[] arguments = Optional.ofNullable(args).map(this::prepareArguments).orElse(new Object[0]);

        if (method.getReturnType().equals(Void.TYPE)) {
            try {
                contract.callFunction(methodName, arguments).get();
            }
            catch (ExecutionException e) {
                throw e.getCause();
            }

            return Void.TYPE;
        } else {
            return findConverter(method.getReturnType()).map(converter -> {
                if (converter.isFutureType(method.getReturnType())) {
                    return converter.convert(contract.callFunction(methodName, arguments).thenApply(result -> convertResult(result, method)));
                }
                return converter.getPayable(contract, methodName, arguments, method, this);
            }).orElseGet(() -> convertResult(contract.callConstFunction(methodName, wei(0), arguments), method));
        }
    }

    private Optional<FutureConverter> findConverter(Class type) {
        return futureConverters.stream().filter(converter -> converter.isFutureType(type) || converter.isPayableType(type)).findFirst();
    }

    private Object[] prepareArguments(Object[] args) {