I am trying to send test USDT to a particular account in Java using the following code:
final Web3j web3 = createWeb3If(ethNetworkUrl); final Credentials credentials = Credentials.create(privateKey); final ERC20 usdtContract = ERC20.load(usdtContractAddress, web3, credentials, new TestGasProvider()); usdtContract.transfer(exchangeAddress, BigInteger.valueOf(10)).send();
The last statement results in the following exception:
java.lang.RuntimeException: Error processing transaction request: intrinsic gas too low at org.web3j.tx.TransactionManager.processResponse(TransactionManager.java:176) at org.web3j.tx.TransactionManager.executeTransaction(TransactionManager.java:81) at org.web3j.tx.ManagedTransaction.send(ManagedTransaction.java:128) at org.web3j.tx.Contract.executeTransaction(Contract.java:367) at org.web3j.tx.Contract.executeTransaction(Contract.java:350) at org.web3j.tx.Contract.executeTransaction(Contract.java:344) at org.web3j.tx.Contract.executeTransaction(Contract.java:339) at org.web3j.tx.Contract.lambda$executeRemoteCallTransaction$3(Contract.java:410) at org.web3j.protocol.core.RemoteCall.send(RemoteCall.java:42) at com.dpisarenko.minimalcryptoexchange.delegates.TransferUsdtToExchangeAccount.execute(TransferUsdtToExchangeAccount.java:57)
TestGasProvider is defined as:
public class TestGasProvider extends StaticGasProvider { public static final BigInteger GAS_PRICE = BigInteger.valueOf(10L); public static final BigInteger GAS_LIMIT = BigInteger.valueOf(1L); public TestGasProvider() { super(GAS_PRICE, GAS_LIMIT); } }
usdtContract
was deployed using this script, which calls deploy.js:
async function main() { const USDT = await ethers.getContractFactory("USDT"); const usdt = await USDT.deploy(1000000000000000); console.log("USDT contract deployed to:", usdt.address); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
This contract is running on a local testnet set up as described here.
What do I need to change in any of these components (testnet, contract, deploy scripts, Java code) in order to send any amount of USDT to a particular address (without any errors)?
Update 1: If I change TestGasProvider
to
public class TestGasProvider extends StaticGasProvider { public static final BigInteger GAS_PRICE = BigInteger.valueOf(1L); public static final BigInteger GAS_LIMIT = BigInteger.valueOf(1000000000L); public TestGasProvider() { super(GAS_PRICE, GAS_LIMIT); } }
I get another error:
java.lang.RuntimeException: Error processing transaction request: exceeds block gas limit at org.web3j.tx.TransactionManager.processResponse(TransactionManager.java:176) at org.web3j.tx.TransactionManager.executeTransaction(TransactionManager.java:81) at org.web3j.tx.ManagedTransaction.send(ManagedTransaction.java:128) at org.web3j.tx.Contract.executeTransaction(Contract.java:367) at org.web3j.tx.Contract.executeTransaction(Contract.java:350) at org.web3j.tx.Contract.executeTransaction(Contract.java:344) at org.web3j.tx.Contract.executeTransaction(Contract.java:339) at org.web3j.tx.Contract.lambda$executeRemoteCallTransaction$3(Contract.java:410) at org.web3j.protocol.core.RemoteCall.send(RemoteCall.java:42) at com.dpisarenko.minimalcryptoexchange.delegates.TransferUsdtToExchangeAccount.execute(TransferUsdtToExchangeAccount.java:57)
Update 1
I am looking to submit a set of code changes to the branch i16 of the minimal-crypto-exchange
project which passes the following test:
Step 1
Set up the environment as described here.
Step 2
Set a breakpoint on line usdtContract.transfer(exchangeAddress, BigInteger.valueOf(10)).send();
in TransferUsdtToExchangeAccount class:
Step 3
Start the process engine application in debug mode. Its Java main method is located here.
Wait until you see the message starting to acquire jobs
in the console output:
11:59:16.031 [JobExecutor[org.camunda.bpm.engine.spring.components.jobexecutor.SpringJobExecutor]] INFO org.camunda.bpm.engine.jobexecutor - ENGINE-14018 JobExecutor[org.camunda.bpm.engine.spring.components.jobexecutor.SpringJobExecutor] starting to acquire jobs
Step 4
Login with the credentials demo/demo
at http://localhost:8080.
After login you should see a page like this:
Step 5
Click on the tasklist link. You should see a page that looks like this:
Press the “Start process” link. Following screen will appear:
Click on Send USDT to the exchange account process
link. Following dialog box will appear:
Enter an arbitrary value into the “business key” field and press the “Start” button.
Step 6
After a couple of seconds, the breakpoint from step 2 will activate.
The problem will be solved if usdtContract.transfer(exchangeAddress, BigInteger.valueOf(10)).send()
is executed without errors.
Notes
- You are allowed to modify the amount in
usdtContract.transfer(exchangeAddress, BigInteger.valueOf(10)).send();
from10
to something else. - You can also modify the parameters of the Ethereum testnet specified in docker-compose.yml and genesis.json, as well as those of the USDT smart contract which is deployed using this script.
- Your solution must work in this controlled environment (i. e. no faucets must be used).
Update 2
I made following changes:
- The set-up tutorial now contains step 7 in which ETH is added to the exchange account.
- Now a new version of the ETH testnet is being used, major changes being that log output is more verbose and the gas price is set to 1 (see
--miner.gasprice 1
in entrypoint.sh). - Modified the code in TransferUsdtToExchangeAccount so that now USDT is transferred not from the exchange account (which has zero balance), but from the buffer account.
Now I am receiving the error
org.web3j.protocol.exceptions.TransactionException: Transaction 0x4bce379a2673c4564b2eb6080607b00d1a8ac232fbddf903f353f4eeda566cae has failed with status: 0x0. Gas used: 32767. Revert reason: 'ERC20: transfer amount exceeds allowance'.
Advertisement
Answer
My skills with Ethereum are still not sharp enough to give you a proper answer, but I hope you get some guidance.
The error states that you are trying to transfer by a party A certain quantity in the name of another party B, to a third one C, but the amount you are trying to transfer, using transferFrom
, is greater than the one party B approved
party A to send.
You can check the actual allowance
between to parties using the method with the same name of your contract.
Please, consider review this integration test from the web3j library in Github. It is different than yours but I think it could be helpful.
Especially, it states that the actual transferFrom
operation should be performed by the beneficiary of the allowance. Please, see the relevant code:
final String aliceAddress = ALICE.getAddress(); final String bobAddress = BOB.getAddress(); ContractGasProvider contractGasProvider = new DefaultGasProvider(); HumanStandardToken contract = HumanStandardToken.deploy( web3j, ALICE, contractGasProvider, aliceQty, "web3j tokens", BigInteger.valueOf(18), "w3j$") .send(); //... // set an allowance assertEquals(contract.allowance(aliceAddress, bobAddress).send(), (BigInteger.ZERO)); transferQuantity = BigInteger.valueOf(50); TransactionReceipt approveReceipt = contract.approve(BOB.getAddress(), transferQuantity).send(); HumanStandardToken.ApprovalEventResponse approvalEventValues = contract.getApprovalEvents(approveReceipt).get(0); assertEquals(approvalEventValues._owner, (aliceAddress)); assertEquals(approvalEventValues._spender, (bobAddress)); assertEquals(approvalEventValues._value, (transferQuantity)); assertEquals(contract.allowance(aliceAddress, bobAddress).send(), (transferQuantity)); // perform a transfer as Bob transferQuantity = BigInteger.valueOf(25); // Bob requires his own contract instance HumanStandardToken bobsContract = HumanStandardToken.load( contract.getContractAddress(), web3j, BOB, STATIC_GAS_PROVIDER); TransactionReceipt bobTransferReceipt = bobsContract.transferFrom(aliceAddress, bobAddress, transferQuantity).send(); HumanStandardToken.TransferEventResponse bobTransferEventValues = contract.getTransferEvents(bobTransferReceipt).get(0); assertEquals(bobTransferEventValues._from, (aliceAddress)); assertEquals(bobTransferEventValues._to, (bobAddress)); assertEquals(bobTransferEventValues._value, (transferQuantity)); //...
This fact is also indicated in this OpenZeppelin forum post.