Question regarding ERC2771Forwarder contract from Openzeppelin/contracts

Hello ,
I am currently working with ERC2771Forwarder for supporting meta-transactions in my dapp .
I want to use executeBatch() function to execute operations in a batch ( in my case transferring nfts in a batch ) but I am having a hard time managing nonces ( provided by Nonces.sol ) .
When a user make 2 requests concurrently at an instant , I get the same nonce corresponding to that user ( from Nonces.sol ) and only one of the requests finally gets executed . How can I get correct nonce value so that my dapp scales well and all the requests also get executed properly ?

hi there I'm using ERC2771 in a project right now

I had executeBatch to work correctly by adding +1 to the second request data

I can share this piece of code:

// prepare request datas for forwarder
ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](2);

requests[0] = _forgeRequestData({
    signer: _dean,
    signerPrivateKey: _deanPrivateKey,
    receiver: address(registry),
    value: 0,
    nonce: myForwarder.nonces(_dean),
    deadline: uint48(block.timestamp + 1),
    data: abi.encodeCall(FNSRegistry.transferFrom, (_dean, users.bob, token1Id))
});
requests[1] = _forgeRequestData({
    signer: _dean,
    signerPrivateKey: _deanPrivateKey,
    receiver: address(registry),
    value: 0,

    // this does the trick
    nonce: myForwarder.nonces(_dean)+1,

    deadline: uint48(block.timestamp + 1),
    data: abi.encodeCall(FNSRegistry.transferFrom, (_dean, users.bob, token2Id))
});

// transfer token with metatransaction
myForwarder.executeBatch{value: 0}(requests, payable(address(0)));

In particular, look at line nonce: myForwarder.nonces(_dean) + 1, where I manually add +1 to the original nonce

And this makes sense because nonces in Nonces.sol are added by +1 for subsequent requests

1 Like

The operations are strictly sequential, which means that the forwarder won't process two transactions with the same nonce. This is by design.

Although the executeBatch may suggest it can be used for multiple parallel operations, it was intended for relayers that are sending multiple requests signed by many users (e.g. over an interval). Since the relayer pays, it's for its best interest to batch as many operations together from different subsidized users as possible.

You can still support batching single-EOA operations If you want, by making your users sign multiple operations with sequential nonces to finally batch them together (as @inmarelibero's approach), but if you want your users to sign a single message, consider using Multicall. :warning: Note that this is only safe if using openzeppelin/contracts@4.9.5 or openzeppelin/contracts@^5.0.1

1 Like

many many thanks @inmarelibero , @ernestognw .
I think I can create gasless flow for my app something like :
Users will send their signed messages ( i.e. requests ) , these messages will get stored in a queue , I'll just make a batch of N number of requests ( let's say each batch contains 10 user transaction requests ) , then send these batches one by one through the forwarder contract ( when a batch gets executed , then only move to sending the next batch ) .

Side by side I will create a nonce manager of my own using NodeJS + MongoDB in which the nonce increases by 1 for every request i.e. if I have 2 batches of 10 requests each , my nonce for the third batch's first request will be 20 . This way I don't need to care whether a transaction got included or not as the nonce always increases in case of executeBatch() method irrespective of a failed or passed transaction call .

Do you both think this approach is okay ?

Can you share any reference/guide for starting with the Multicall approach ?
Is Multicall approach an alternative of executeBatch() method in the ERC2771 forwarder contract ?

@iamgauravpant I fear it's not the best approach, because when users signs the message you want to put in a queue, it must contain the nonce

and if it must contain the nonce your nonce manager can't work as you intend

I guess @ernestognw refers to this https://blog.verichains.io/i/139637529/multicall when mentioning multicall

@inmarelibero is right. Signed forward requests are strictly sequential, not incremental. This means that transaction 5 can't be processed after transaction 3 if transaction 4 hasn't been processed.

Note the nonce of a ForwardRequest is per EOA. Not per forwarder.

The idea of a queue is good, honestly. It's just that you'll need to manage when a single user queues more than one transaction, since they might be signing the incorrect nonce.

Multicall is different to executeBatch in the sense that the former executes multiple operations within a single signature, and the latter executes multiple signed operations. You can find more about multicall in the docs, but ideally, each of your users submit a single signed multicall instead of multiple signed request to batch.

I guess @ernestognw refers to this https://blog.verichains.io/i/139637529/multicall when mentioning multicall

Yes, the 4.9.5 and >5.0.1 are patched against the Thirdweb issue, so it's safe to use.

I suggest you to take a look at Defender Relayers, you can delegate the transaction forwarding to one of these as long as you fund it and stay within the free quota (e.g. 1 transaction every 10 seconds is 360 txs/hour, not sure if it fits). This way you can get rid of transaction processing in your backend and only handle your users signatures.