4

I am rather new to writing/using smart contracts in Java and I am currently trying to use my smart contract. I already wrote and tested it, compiled it, deployed it to Ropsten, and built my Java class from it using web3j-cli. So far, no problems.

Now, my contract features a function run(string memory x) that will return the string "ok" in case i pass the parameter "test" and "not ok" if any other string is passed. just for testing / exercise purposes for now.

Now what I am doing in my Java code is building a Web3j client and connect to Ethereum/Ropsten via my Infura account.

try {
    HttpService httpService = new HttpService(INFURA_ENDPOINT); // Infura endpoint for ropsten
    String auth = new String(":" + "yyyxxxzzz"); // Infura secret
    byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1));
    String authHeader = "Basic " + new String(encodedAuth);
    httpService.addHeader(HttpHeaders.AUTHORIZATION, authHeader);
            
    Web3j web3j = Web3j.build(new HttpService(INFURA_ENDPOINT));
            
    String privateKeyString = "xyz"; // private key as exported from Metamask for my account in ropsten
    String address = "0x8e329a83B96fc80cc00C0acd5efbf78699b2F299"; // address for said account
    Credentials credentials = Credentials.create(privateKeyString);
            
    String contractAddress = "0xabcdef"; // address of the deployed contract in ropsten
            
    final BigInteger gasPrice = BigInteger.valueOf(2205000);
    final BigInteger gasLimit = BigInteger.valueOf(14300000);
    final ContractGasProvider gasProvider = new StaticGasProvider(gasPrice, gasLimit);
    
    final Test contract = Test.load(contractAddress, web3j, credentials, gasProvider);
            
    String a = contract.run("xyz").send();
    System.out.println("run(xyz): " + a);

} catch(Exception e) {
    System.out.println("Web3j: " +  e.getMessage() + "\n");
    e.printStackTrace();
}

Now, explicitly calling the function run() looks like this in my generated contract Java class:

public RemoteFunctionCall<String> run(String name) {
    final Function function = new Function(FUNC_RUN, 
            Arrays.<Type>asList(new org.web3j.abi.datatypes.Utf8String(name)), 
            Arrays.<TypeReference<?>>asList(new TypeReference<Utf8String>() {}));
    return executeRemoteCallSingleValueReturn(function, String.class);
}

EDIT: Here's the Solidity code of the smart contract:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Test {
    function run(string memory name) public pure returns (string memory) {
        if(strcmp(name, "test")) {
            return "ok";
        }
        
        return "not ok";
    }
    
    function strcmp(string memory a, string memory b) internal pure returns (bool) {
        if(bytes(a).length != bytes(b).length) {
            return false;
        }
        else {
            return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
        }
    }
}

Works as expected in remix.ethereum. This contract has been deployed in Ropsten at 0x8e329a83B96fc80cc00C0acd5efbf78699b2F299

Now, when I run this, a call such as contract.run("xyz")runs without problems and is also received by Infura - as I can easily check and verify in my dashboard. Yet as soon as I append a send() (same with sendAsync().get() and the lot) I get the following exception:

Exception in thread "main" java.lang.NoSuchMethodError: 'okhttp3.RequestBody okhttp3.RequestBody.create(java.lang.String, okhttp3.MediaType)'
    at org.web3j.protocol.http.HttpService.performIO(HttpService.java:154)
    at org.web3j.protocol.Service.send(Service.java:48)
    at org.web3j.protocol.core.Request.send(Request.java:87)
    at org.web3j.tx.RawTransactionManager.sendCall(RawTransactionManager.java:155)
    at org.web3j.tx.ManagedTransaction.call(ManagedTransaction.java:134)
    at org.web3j.tx.Contract.executeCall(Contract.java:292)
    at org.web3j.tx.Contract.executeCallSingleValueReturn(Contract.java:300)
    at org.web3j.tx.Contract.executeCallSingleValueReturn(Contract.java:311)
    at org.web3j.tx.Contract.lambda$executeRemoteCallSingleValueReturn$1(Contract.java:399)
    at org.web3j.protocol.core.RemoteCall.send(RemoteCall.java:42)
    at com.example.web3j.TestWeb3j.main(TestWeb3j.java:56)

I'm mostly following the tutorials https://dzone.com/articles/blockchain-simplified-with-ethereum-example-with-j and https://trimplement.com/blog/2020/03/coding-smart-contracts-tutorial-infura/ here. As far as I understand here, a send() should call the contract's function and get me the returned string. I already asked this in the Infura-board with no success (https://community.infura.io/t/calling-functions-on-smart-contract-fails/2401).

What am I doing wrong?

Edit: Or asked differently: Can anyone provide me with a minimal working example of how to call a function in a smart contract (ideally, THIS function in THIS smart contract) on Ropsten using Web3j on Java 15 via Infura?

I am running all that in a SpringBoot 2.4.1 on Web3j 4.8.2 on Java 15

Edit: SpringBoot 2.4.3/Web3j 4.8.4 on IntelliJ IDEA 2020.3.2: Same problems

Edit 2: Proposed solution doesn't work unfortunately. Here is the entire project's code:

@SpringBootApplication
public class SpringBootWeb3j {

    private final static String INFURA_ENDPOINT = "https://ropsten.infura.io/v3/";
    private final static String INFURA_PROJECT_ID = "myProjectID";
    private final static String INFURA_PROJECT_SECRET = "myProjectSecret";
    
    public static void main(String[] args) {
    
        try {
            HttpService httpService = new HttpService(INFURA_ENDPOINT + INFURA_PROJECT_ID);
            String auth = new String(":" + INFURA_PROJECT_SECRET);
            byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1));
            String authHeader = "Basic " + new String(encodedAuth);
            httpService.addHeader(HttpHeaders.AUTHORIZATION, authHeader);
            
            Web3j web3j = Web3j.build(httpService);
            
            ECKeyPair ecKeyPair = Keys.createEcKeyPair();
            Credentials credentials = Credentials.create(ecKeyPair);
            
            String contractAddress = "0x8e329a83B96fc80cc00C0acd5efbf78699b2F299";
            
            final BigInteger gasPrice = BigInteger.valueOf(2205000);
            final BigInteger gasLimit = BigInteger.valueOf(14300000);
            final ContractGasProvider gasProvider = new StaticGasProvider(gasPrice, gasLimit);
            
            TransactionManager manager = new RawTransactionManager(web3j, credentials, 200, 500);
            
            final Test contract = Test.load(contractAddress, web3j, manager, gasProvider);
            
            String a = contract.run("xyz").send(); // <-- throws exception
            System.out.println("run(xyz): " + a);
        }
        catch(Exception e) {
            System.out.println("Problem with Web3j: " +  e.getMessage() + "\n");
            e.printStackTrace();
        }
        
        SpringApplication.run(SpringBootWeb3j.class, args);
    }
}

Entire compiled Smart Contract:

/**
 * <p>Auto generated code.
 * <p><strong>Do not modify!</strong>
 * <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>,
 * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the 
 * <a href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
 *
 * <p>Generated with web3j version 4.5.16.
 */
@SuppressWarnings("rawtypes")
public class Test extends Contract {
    public static final String BINARY = "608060405234801561001057600080fd5b506102a0806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80639352fad214610030575b600080fd5b61004361003e366004610134565b610059565b60405161005091906101f1565b60405180910390f35b606061008182604051806040016040528060048152602001631d195cdd60e21b8152506100c9565b156100a557506040805180820190915260028152616f6b60f01b60208201526100c4565b506040805180820190915260068152656e6f74206f6b60d01b60208201525b919050565b600081518351146100dc5750600061012e565b816040516020016100ed91906101d5565b604051602081830303815290604052805190602001208360405160200161011491906101d5565b604051602081830303815290604052805190602001201490505b92915050565b60006020808385031215610146578182fd5b823567ffffffffffffffff8082111561015d578384fd5b818501915085601f830112610170578384fd5b81358181111561018257610182610254565b604051601f8201601f19168101850183811182821017156101a5576101a5610254565b60405281815283820185018810156101bb578586fd5b818585018683013790810190930193909352509392505050565b600082516101e7818460208701610224565b9190910192915050565b6000602082528251806020840152610210816040850160208701610224565b601f01601f19169190910160400192915050565b60005b8381101561023f578181015183820152602001610227565b8381111561024e576000848401525b50505050565b634e487b7160e01b600052604160045260246000fdfea2646970667358221220eb9d0f71f0d251b9767f571c8b5add883a8f864291930492c9b9775e665bcfe664736f6c63430008000033";

    public static final String FUNC_RUN = "run";

    @Deprecated
    protected Test(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit);
    }

    protected Test(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
        super(BINARY, contractAddress, web3j, credentials, contractGasProvider);
    }

    @Deprecated
    protected Test(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
        super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit);
    }

    protected Test(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
        super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider);
    }

    public RemoteFunctionCall<String> run(String name) {
        final Function function = new Function(FUNC_RUN, 
                Arrays.asList(new org.web3j.abi.datatypes.Utf8String(name)),
                Arrays.asList(new TypeReference<Utf8String>() {}));
        return executeRemoteCallSingleValueReturn(function, String.class);
    }

    @Deprecated
    public static Test load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        return new Test(contractAddress, web3j, credentials, gasPrice, gasLimit);
    }

    @Deprecated
    public static Test load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
        return new Test(contractAddress, web3j, transactionManager, gasPrice, gasLimit);
    }

    public static Test load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
        return new Test(contractAddress, web3j, credentials, contractGasProvider);
    }

    public static Test load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
        return new Test(contractAddress, web3j, transactionManager, contractGasProvider);
    }

    public static RemoteCall<Test> deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
        return deployRemoteCall(Test.class, web3j, credentials, contractGasProvider, BINARY, "");
    }

    @Deprecated
    public static RemoteCall<Test> deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        return deployRemoteCall(Test.class, web3j, credentials, gasPrice, gasLimit, BINARY, "");
    }

    public static RemoteCall<Test> deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
        return deployRemoteCall(Test.class, web3j, transactionManager, contractGasProvider, BINARY, "");
    }

    @Deprecated
    public static RemoteCall<Test> deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
        return deployRemoteCall(Test.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, "");
    }
}

And while we're at it, here the build.gradle:

plugins {
    id 'org.springframework.boot' version '2.4.3'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '15'

repositories {
    mavenCentral()
}

dependencies {
    implementation group: 'org.web3j', name: 'core', version: '4.8.4'

    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

Project example available at https://gitlab.com/ErrorUsernameAlreadyTaken/springbootweb3j

7
  • Can u upload the project to a GitHub repo.. make sure to remove any personal data before doing that – Majd TL Mar 1 at 13:12
  • does the account you generate has Ether to pay for the transaction? – Majd TL Mar 1 at 13:23
  • Will upload in a bit. Yes, the account used has Ether in Ropsten – Xenonite Mar 1 at 16:30
  • Another question, why do u need this line httpService.addHeader(....) , I already use infura without this line in my code – Majd TL Mar 1 at 17:59
  • 1
    Can u try without it? In my case I don’t expose the project Id and I haven’t used such a thing before, anyway it could be an extra new feature in infura. if you can public your code on GitHub then I can try it tomorrow (in about 12 hours) – Majd TL Mar 1 at 19:47
3
+100

modify your build.gradle to this and rebuild (i usually use maven), the problem was probably with okhttp used in web3j

dependencies {
    // https://mvnrepository.com/artifact/org.web3j/core
    implementation 'org.web3j:core:4.8.4'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencies{
    implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.3.1'
    implementation("com.squareup.okhttp3:logging-interceptor:4.3.1")
}

test {
    useJUnitPlatform()
}

That will solve the problem, but another error could accrue.so :

Try to find which solidity version is supported by Web3j (cause 0.8 is very new) and then deploy the contract with the new version and generate the java code to that contract using Web3j

here are some docs about web3j contract interaction http://docs.web3j.io/latest/smart_contracts/interacting_with_smart_contract/

this method could be helpful to check if the contract is valid before calling your method: http://docs.web3j.io/latest/smart_contracts/contract_validity/ http://docs.web3j.io/latest/quickstart/#loading-a-smart-contract

Another thing in your contract you are using "public pure" method which does not need a tx, try better the storage contract, it has 2 methods the first one is a transaction and return a tx hash, the other method is a get method to get the value without a transaction https://docs.soliditylang.org/en/v0.4.24/introduction-to-smart-contracts.html

Try it and tell me if everything works

2
  • Really a BIG thanks! This works as promised!!! – Xenonite Mar 2 at 12:43
  • 1
    welcome happy to help – Majd TL Mar 2 at 14:20
0

Use RawTransactionManager

public MyComponent(Web3j web3j) {
    this.web3j = web3j;
    this.gasProvider = new StaticGasProvider(new BigInteger(gasPrice), new BigInteger(gasLimit));

    ...
  
    Credentials credentials = Credentials.create(privateKeyString);
    TransactionManager manager = new RawTransactionManager(web3j, credentials, 200, 500);
    Test contract = Test.load(contractAddress, web3j, manager, gasProvider);

    String a = contract.run("xyz").send();
    System.out.println("run(xyz): " + a);
  }

Web3j should be directly injected by Spring. Define in application.yml:

web3j:
  client-address: PUT_YOUR_INFURA_ENDPOINT_HERE
  admin-client: false

The code runs with Spring Boot 2.4.2, Web3j 4.5.5 and Java 11

1
  • Thank you for your answer. Anyhow, this produces the exact same error. I added the entire source code (truncated project id/secret) to the original post – Xenonite Mar 1 at 12:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.