I'm using the Defender relayer to perform a transaction on a polygon contract.
It works smoothly 99% of the time however we're experiencing some very odd behavior when it does fail.
We got relayer funds low alert at the time but it could have been also the 120 tx/hour limit.
The relayer was topped up, and at no point there was not enough MATIC to perform the transaction.
However, our app did receive the following errors
Request failed with status code 400 DefenderApiResponseError: Request failed with status code 400
at rejectWithDefenderApiError (/workspace/node_modules/defender-base-client/lib/api/api.js:11:27)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async /workspace/node_modules/defender-relay-client/lib/api/index.js:92:21
at async ApiRelayer.apiCall (/workspace/node_modules/defender-base-client/lib/api/client.js:26:20)
at async DefenderRelaySigner.sendTransaction (/workspace/node_modules/defender-relay-client/lib/ethers/signer.js:122:15)
This was caught by our app, communicated to the user, and was attempted later and succeeded.
However, to our surprise it looks like this was a false negative, because all of those transactions did go through which is very very bad (imagine sending double tokens!)
Is this a bug on your end?
Is this expected behavior? How can prevent this from happening and trust that an error means failed, and if it's not failed how can I get the tx hash of this transaction.
Thanks,
Jonathan
Environment
OpenZeppelin Defender relayer on Polygon using ethers.js and DefenderRelayProvider
Details
For example, the below transaction was sent when my app received an error back
Later the action was attempted again by the client (slighly different tokens selected, but some were the same and the ended up with double spend)
For the 0xf5af6f340367b4e83f1fcd98c866d023ed0d0c3cdf9cad25849392178ec4ff51 transaction I received the error above and assumed it failed (didn't get the tx hash back but an exception)
Code to reproduce
const assetsContract = await getAssetsContract();
const minterWallet = await getMinterWallet();
const contractWithSigner = assetsContract.connect(minterWallet);
const transaction = await contractWithSigner.mintToMultipleWallets(
walletAddresses,
tokenIds,
tokenAmounts
);
return transaction.hash;
Where assetsContract is :
const credentials = {
apiKey: RELAYER_API_KEY,
apiSecret: RELAYER_SECRET_KEY,
};
const provider = new DefenderRelayProvider(credentials);
const assets = new Contract(
contractAddress,
contractAbi,
provider
);
return assets;
Minter wallet is:
const credentials = {
apiKey: RELAYER_API_KEY,
apiSecret: RELAYER_SECRET_KEY,
};
const provider = new DefenderRelayProvider(credentials);
const signer = new DefenderRelaySigner(credentials, provider, {
speed: config.web3_transaction_speed,
});
return signer;