How to send transactions concurrently

Hi,

I’m building an API that calls a function when a user calls the API. It calls a so called claim function. But when I try to send 2 API requests at the same time the program fails because it still has a pending tx and it gives the following error: Error: Returned error: replacement transaction underpriced.

But I do not want to replace the prior transaction. I simply want to just send another transaction to the mempool without waiting for the prior transaction to complete. Does anyone know how to do this? I’ve read about nonce but giving a nonce will not work and still ask for a replacement.

app.post("/claim", cors(corsOptions), (req, res) => {
  console.log(req.query.address);
  executeClaim(req.query.address);

  res.json({
    test: req.body,
  });
});

async function executeClaim(claimerAddress) {
  console.log("initiating new claim");
  web3.eth.accounts.wallet.add(account);
  web3.eth.defaultAccount = account.address;
  contract.defaultChain = "rinkeby";
  contract.defaultHardfork = "london";

  const gasLimit = await contract.methods.claim(claimerAddress).estimateGas({
    from: account.address,
  });

  console.log(gasLimit);
  const nonce = await web3.eth.getTransactionCount(web3.eth.defaultAccount,);

  await contract.methods
    .claim(claimerAddress)
    .send({ from: account.address, gasLimit: gasLimit, nonce: nonce })
    .then(function (receipt) {
      console.log("Succesfully Claimed: ", receipt);
    })
    .catch((err) => {
      console.log("Error [claim]: ", err);
    });
}

So what happens is:
Person 1 and Person 2 call the API at the same time. The API will send Person 1’s tx (claim function) then tries to send Person 2’s tx but in the catch I get the error: Error: Returned error: replacement transaction underpriced

A few things first; you should let your executeClaim be the handler for that route.

app.get("/claim", executeClaim) => {

async function executeClaim(req, res) {

And then you can set cors as middleware.

app.use("/claim", cors(corsOptions));

I’m a bit confused, you are allowing the same user/account to call claim multiple times simultaneously? What does claim do? For a second to work you’d need to set an increased nonce making it a different transaction. What you’re doing there is just using the existing nonce for both.

This would need to be done outside of the executeClaim so the server knows how to increment the nonce in each call if necessary.

Yes, It needs to execute every request. Its like a faucet, people connect their wallet to the front-end and then press a claim button to request some matic tokens. The API then gets called and calls a claim function on the contract. The api will only accept requests from the front-end thus the corsOptions. The function claim on the contract is onlyOwner so only the address from the API is allowed to call the claim function.

I already got it working with passing a nextNonce:

    const nextNonce = await web3.eth.getTransactionCount(
      web3.eth.defaultAccount,
      "pending"
    );

One problem I still have however is that someone that knows the url of the api could directly call the claim method. Is there a way to disable directly calling the api? I don’t want someone constantly making new wallets and claiming. (a wallet can only claim once)

I don’t want someone constantly making new wallets and claiming. (a wallet can only claim once)

Is this not built into the contract? Why not have the claim based on the user wallet e.g. in a mapping instead of using your own? So user can only claim if their wallet hasn’t already claimed. This would get around the concurrency issue.

Unless you have other protections e.g. I don’t think you can really stop this for a faucet. Otherwise you’ll have to limit based on things like IP address, wallet address and stop it at your frontend/backend. And if your contract is ever exposed people can call it directly.

Is there a way to disable directly calling the api?

You can so only requests from your frontend/domain work. I believe that’s what you’re trying to do with CORS, this would only stop browser based requests to the API however.

Yes the claim is mapped in the contract per address. But the issue here is people have 0 funds so they can’t call the contract. That’s why I let them call an API so I can send them through the contract some funds. The limit on the contract is there so people would not be able to claim more than once. I guess rate limiting with IP is further improving the spam with botted wallets.

But the issue here is people have 0 funds so they can’t call the contract.

I see, that makes sense. So there’s not much you can do to prevent scripts that create new wallets, use different IPs and blast your interface so lock down everything as much as possible. Some faucets use requiring a social media post/connection, captchas, etc. so consider these options as well.