Relay creating 2 TX with 1 API call (bug?)

I have a situation where my backend code called the Relayer with a transaction. Transaction got accepted (after some bad requests initially) and Relayer returned the tx hash. When I look at my contract I can see that relayer actually made 2 calls to the contract with the same data but I only got 1 response with the most recent tx hash.

Not sure what is going wrong here but I now have 2 ERC-721 tokens created where user only wanted 1.

1 Like

Digging in my log files I can see my code called the Relayer but got no response data and then later called relayer again but now with a response. This would explain the double mint.

But how to catch situations like this where Relayer accepts my call but gives no response?

Hey @guusman, thanks for reaching out.

Can you share the code you used or your logs to find out what was wrong? I'd like to confirm what kind of issue is and how it's affecting our systems if that's the case. I'd like to know exactly what no response means.

My only guess is a connection error while getting the response, but that's unlikely.

Looking forward to it @gussman

1 Like

My code is AWS Lambda function. Relevant part:

console.log(`Wallet (${notaryUser}) selected!`);
const stampContract = new ethers.Contract(
const tx = await stampContract.stamp(notaryUser, name, multiHash, {
  gasLimit: 8000000,
console.log(`Relay TX: ${JSON.stringify(tx)}`);

In the log I see the following, where you can see the last console.log does not appear but the task times out.

2022-03-23T14:07:23.893+01:00	2022-03-23T13:07:23.893Z 6ceeaffb-d402-4034-a0e9-0dddf7504a4c INFO Wallet (0x5393A421F5d825CaC573750cE7096CcE831CD244) selected!
2022-03-23T14:07:42.403+01:00	END RequestId: 6ceeaffb-d402-4034-a0e9-0dddf7504a4c
2022-03-23T14:07:42.403+01:00	REPORT RequestId: 6ceeaffb-d402-4034-a0e9-0dddf7504a4c Duration: 25025.52 ms Billed Duration: 25000 ms Memory Size: 128 MB Max Memory Used: 126 MB
2022-03-23T14:07:42.403+01:00	2022-03-23T13:07:42.403Z 6ceeaffb-d402-4034-a0e9-0dddf7504a4c Task timed out after 25.03 seconds 

Because the task did not complete succesfully, AWS Lambda retries and the following log appeared:

2022-03-23T14:07:44.773+01:00	2022-03-23T13:07:44.773Z 6ceeaffb-d402-4034-a0e9-0dddf7504a4c INFO Wallet (0x5393A421F5d825CaC573750cE7096CcE831CD244) selected!
2022-03-23T14:07:59.033+01:00	2022-03-23T13:07:59.015Z 6ceeaffb-d402-4034-a0e9-0dddf7504a4c ERROR Unhandled Promise Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"[object Object]","reason":{"response":{"status":400,"statusText":"Bad Request","data":{"message":"Insufficient funds: 0.8 ETH required but 0.739146227244073 ETH are available."}},"message":"Request failed with status code 400","request":{"path":"/txs","method":"POST"}},"promise":{},"stack":["Runtime.UnhandledPromiseRejection: [object Object]"," at process.<anonymous> (/var/runtime/index.js:35:15)"," at process.emit (events.js:412:35)"," at process.emit (domain.js:475:12)"," at processPromiseRejections (internal/process/promises.js:245:33)"," at processTicksAndRejections (internal/process/task_queues.js:96:32)"]}
2022-03-23T14:07:59.114+01:00	END RequestId: 6ceeaffb-d402-4034-a0e9-0dddf7504a4c
2022-03-23T14:07:59.114+01:00	REPORT RequestId: 6ceeaffb-d402-4034-a0e9-0dddf7504a4c Duration: 15338.44 ms Billed Duration: 15339 ms Memory Size: 128 MB Max Memory Used: 79 MB
2022-03-23T14:07:59.114+01:00	Unknown application error occurred 

This is an odd error as there are enough funds in the Relayer. AWS lambda keeps retrying the task and after 4 times it succeeds:

2022-03-23T14:08:52.974+01:00	2022-03-23T13:08:52.974Z 6ceeaffb-d402-4034-a0e9-0dddf7504a4c INFO Wallet (0x5393A421F5d825CaC573750cE7096CcE831CD244) selected!
2022-03-23T14:09:06.814+01:00	2022-03-23T13:09:06.776Z 6ceeaffb-d402-4034-a0e9-0dddf7504a4c INFO Relay TX: {"chainId":43114,"hash":"0xff8100880f29c49b9ff7a548ad5d1fc860e3e6ef1da7e626b973b64ba4016563","transactionId":"e6e000ea-83d1-4265-b0f0-7667da209aed","value":{"type":"BigNumber","hex":"0x00"},"gasPrice":{"type":"BigNumber","hex":"0x174876e800"},"gasLimit":{"type":"BigNumber","hex":"0x7a1200"},"to":"0xf3b7218827EBB87494a5B9cCfbA2E4F7B93E4e01","from":"0x933a46ec98387b560bb2f270514f64df3f500a4c","data":"0xae3dc64c0000000000000000000000005393a421f5d825cac573750ce7096cce831cd244000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005466f746f200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044313232303335373839326533616134393333393234653430616436663164343533346434666333656666303233353934666661303766336366363730373631393938626300000000000000000000000000000000000000000000000000000000","nonce":3,"status":"sent","speed":"fast","validUntil":"2022-03-23T21:09:05.846Z","createdAt":"2022-03-23T13:09:06.237Z","sentAt":"2022-03-23T13:09:06.237Z","pricedAt":"2022-03-23T13:09:06.237Z"}

Relayer returned tx hash '0xff8100880f29c49b9ff7a548ad5d1fc860e3e6ef1da7e626b973b64ba4016563'. If you look at the contract I am calling you can see that 2 min before this tx there is another tx. This tx (0x3f23d3bba93f881d96266a652d696b91964be0f123d9fbf229c867243277a019) I never received back from the relayer and the times match the timeout in my logs. Both TX have minted the same token (I validated this manually).

Thanks for the details @guusman, I can confirm it's because the lambda you were using retried, and this is not catchable as it was due to a timeout. It's important to note that the Relay service will use a fast speed when none is specified, but in certain network conditions, it might take some time for the tx to pass through, but in the midtime, the .stamp promise won't be resolved.

The Defender Relay service is designed so you don't have to worry about resubmission logic, just send it once and you can check its status afterwards by calling the /tx/{txId} endpoint we have (here's the docs)

In regards to the Insufficient funds message you got, it is because you specified a very high gasLimit, and the relayer service is designed to warn you in case you have commited a large amount of AVAX before sending another tx. In this case 8000000 * 0.0000001 = 0.8 AVAX, and your Relay had ~1.4.

My recommendation for future executions is:

  1. Submit your transaction once, it's guaranteed to stay in our system until it passes or a NOOP happens (reasons for this could vary, but is unlikely. An example is a tx expiration)
  2. If you're waiting to much for the tx to pass, you can resubmit it with a higher gasPrice or speed (our system reprices every few minutes anyway)
  3. If you want to know the current status, use the txs/{txId} endpoint we have
  4. It's not necessary to specify a gasLimit as Defender sets it automatically

Hope this helps!

1 Like

Yes really helpful. One remark on the recommendations

  1. My timeout occured because tx was not returned. This ofcourse can always happen but it would nice if I could provide a referenceID from our side when submitting the tx and I can use this referenceID to query the list and understand the relayID and/or txid.

Gotcha! Since you're using the ethers.js signer, we can't manipulate how the .stamp resolves as it is an ethers.js thing, so it might happen with any other signer as well.

I suggest you to send a transaction with data via API instead. Just use ethers.js and the signer to get the encodedData (docs) and send it over the API. This in fact will return you the sent transaction before it gets mined (including transactionId), which you can use for further query

Great suggestion. Implemented this code. sendTransaction needs a gasLimit but I can query the API also for estimated gas so that is no problem.

This makes it more robust. It would however (in my humble opinion) be even better to implement an idempotency-key HTTP header to the API. This makes it more robust I think.

thanks for the help

Hi @guusman, thanks for the suggestion! Indeed we plan to introduce an idempotency-key to the API sometime in Q2. It's probably not going to be an HTTP header but part of the transaction request payload, but we haven't finalized the design yet.

1 Like