Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimal And Extensible Meta Transaction Forwarder #2585

Closed
wighawag opened this issue Apr 3, 2020 · 14 comments
Closed

Minimal And Extensible Meta Transaction Forwarder #2585

wighawag opened this issue Apr 3, 2020 · 14 comments
Labels

Comments

@wighawag
Copy link
Contributor

wighawag commented Apr 3, 2020

Pull Request Link : #2600

Simple Summary

This standard defines a universal smart contract, the "Forwarder", that accept specially crafted signed message by Externally Owned Accounts (EOA) to be executed as contract calls on their behalf by third parties (relayers). These relayers take the responsibility of signing and broadcasting an ethereum transaction carrying the message (a "Meta Transaction" message). The proposal is made so that extra Meta Transaction functionality can be built on top without the need for receiver contract to be changed.

Abstract

Native Meta Transaction allows users that simply own a private key to interact on the Ethereum network by only signing messages (no need for ether or account contract, just a private key). Third parties (the relayers) broadcast these messages through a contract on their behalf.
That contract is in charge to ensure the signing message is well formed and come from the intended signer. It also ensure replay protection so a message cannot be used twice.

This proposal implements a minimal contract with that responsibility, while remaining flexible for future extension. On particular it allows more complex message format (like EIP-1776) to be implemented on top, while meta transaction receiver can remain unchanged and trust only the singleton Forwarder.

msg.sender verification is achieved by replacing the use of solidity msg.sender with a function that extract the signer address from the call data.

Motivation

Several EIPs have been proposed to support Meta Transactions (EIP-1776, EIP-1613, EIP-1077). They all have so far failed to get traction. This can be attributed to several factor but one important aspect is that they all tries to implement much more that simply forwarding message to contracts.
By being complex, they increase the friction for contract developer that need to trust that these solution are future proof.

This proposal aims at finding the most basic features while remaining extensible. As such this proposal will not deal with relayer repayment (that EIP-1776 and other tackle) or relayer coordination (that EIP-1613 tackle). Its sole purpose it to ensure the signed message is valid and that receiver contract have only one contract to trust.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Message format

The proposal is using a message format based on EIP-712 so that wallet that support EIP-712 but do not support the proposal described here can still offer an approval display showing all the information albeit in a less than ideal presentation.

Here is the proposed EIP-712 message format :
MetaTransaction(address from,address to,uint256 value,uint256 chainId,address replayProtection,bytes nonce,bytes data,bytes32 innerMessageHash)

The meaning of each field is as follow:

  • from: the account from which the meta-transaction is executed for, that value will be appended to the calldata of the meta transaction call. It MUST be either equal to the resulting message signer or have been given execution rights to the signer (via ERC1271 or ERC1654)
  • to: the target that will receive the meta transaction call.
  • value: the ether value expected to be given by the relayer to the call. This can be used by more complex meta transaction implementation, to allow user that do own ether to perform tx that requires it. THis can be compensated for example by paying the relayer for it in token or other means. This is outside of the scope of this EIP.
  • chainId: By specifying the chainId, the meta transaction is not going to be executable in a fork whose chainId is different. See rationale for why we do not use EIP-712 chainId from the domain
  • replayProtection: address of a contract that will handle replay protection for the user. If set to address(0) or the address of the forwarder, it will use the default replay protection, see below for more details.
  • nonce: the nonce value used by the signer and passed to replayProtection.checkAndUpdateNonce(bytes). The default is a 2 dimensional nonce represented as a 256 bit integer split in two.
  • data: the bytes to be executed at to. the from address will be appended to it so that receiver contract can access the signer of the meta transaction.
  • innerMessageHash: Its purpose is to allow more complex meta transaction mechanism to embed message into a single message. (see below for details)

transaction execution and receiver verification

After the singleton Forwarder contract check for the validity of the signature, it append the address of the signer (or the account contract if using EIP-1271 or EIP-1654) to the call data.

As the result in order for a contract to receive meta transaction it simply has to extend the following contract and replace text occurrence of msg.sender with _getTxSigner() :

pragma solidity 0.6.4;

contract ForwarderReceiverBase {
    address /* immutable */ _forwarder; // can be hardcoded once the Forwarder address is known
    constructor(address forwarder) public {
        _forwarder = forwarder;
    }

    function _getTxSigner() internal view returns (address payable signer) {
        if (msg.sender == _forwarder) {
            bytes memory data = msg.data;
            uint256 length = msg.data.length;
            assembly { signer := and(mload(sub(add(data, length), 0x00)), 0xffffffffffffffffffffffffffffffffffffffff) }
        } else {
            signer = msg.sender;
        }
	}
}

default replay protection

While the Forwarder allows user to choose any replay protection they chose, this forwarder comes with a default implementation to get started.
The default implementation use a 2 dimensional nonce, so that user can send simultaneous transactions in multiple independent batches.

The nonce is simply an abi encoded uint256 that is split in two 128 bit values. The higher bits represent the batchId while the lower bits represent the nonce in the batch.

Application can request the current nonce by calling the function getNonce(userAddress, batchId)

The nonce to be sent is equal to the current Nonce. For every use the current Nonce get increased by one for that particular batch.

batching calls

THe forwarder also provide a mechanism to batch meta transaction in one message allowing the calls to either succeed fully or fails.
The message need to perform encode a call to the forwarder with this special function : batch that can only be called by the forwarder itself.
Calls are a struct containing the destination (to), the data (data) and the ether value to passed in (value). It is up to the user to ensure it sign a value big enough to cover the sum of all values passed in.

wallet / browser

While the forwarder use EIP-712 message format and wallet can use the information to display some information about the meta transaction. Wallets should integrate EIP-2585 more comprehensively.
In particular they should show a UI similar to existing Ethereum transaction.
Furthermore, since that proposal is built to support extensibility via the innerMessageHash wallet that supports EIP-2585 should add a specific API for emitting meta transaction :

Similar to eth_signTypedData (see article)

wallet would add a new JSON RPC method :

{
    "method": "eth_signMetaTransaction",
    "params:" [<signer>, <eip2585Message>, <innerMessageData>]
}
  • signer would be the account asked to sign the message
  • eip2585Message would be the message as described in that proposal, except that the innerMessageHash is optional, see innerMessageData below
  • innerMessageData is optional. When absent, message need to include the innerMessageHash or it default to the zero filled bytes32. If specified, it follows EIP-712 standard and need to specify both the message and types, like specified in EIP-712. But instead of signing such message, it simply hash it. The resulting hash is then used as the innerMessageHash field for the eip2585 message.

Wallets that support EIP-2585 will thus be able to display new fields name and their values for proposals built on top of EIP-2585. This allow experimentations at a higher level keeping receiver contract compatible.

EIP-1776 or EIP-1613 could thus be built without requiring receiver contracts to change. Their extra fields would be displayed to wallet via the inner EIP-712 message. Obviously, once these standards become mainstream, wallet can decide to support them more natively if necessary.

Rationale

Account Contract Support

While Native Meta Transaction System like this EIP, are designed to allow Externally Owned Accounts (EOA) to emit Meta Transaction, allowing Account contract to use such system is valuable for the following reasons :

  • Account Contract can reuse the relayers already setup for EIP-2585
  • Any EIP built on top of EIP-2585 would benefit Account Contracts too
  • The extra complexity is not big and EIP like EIP-1271 and EIP-1654 already exists

Forwarding msg.value

While on its own forwarding msg.value might not be used with EIP-2585 alone (as relayer would need to be compensated and EIP-2585 does not attempt to tackle that), EIP built on top, like EIP-1776 would benefit in allowing EOA to pay for service in ETH by rewarding relayers in other tokens.

Flexible Replay Protection

While the implementation implements a default 2 dimensional nonce replay protection, it also allows signers to provide their own.

innerMessageHash

The innerMessageHash's purpose is that more complex message format can be used to support more complex meta transaction system. For example relayer repayment mechanism proposed by EIP-1776 can be built on top.

The innerMessageHash is to be used by wallet provider and MUST be the hash of an EIP-712 message, that wallet can then interpret to display the relevant information. See above for a description on how wallet handle such hash. Future message format could be added too.

Signature Format : EIP-712

The message format is based on EIP-712 so that wallet not supporting EIP-2585 can still display some information. Unfortunately, they would not be able to display any information provided by the message hashed into innerMessageHash for EIP-2585 extension, like EIP-1776. Special support from Wallet is needed for that.

Fork Replay Protection (and transition)

The message format do not use EIP-712 domain for chainId protection as this introduce unnecessary implementation complexity for dealing with EIP-712 domain hash refresh. Instead the chainId is provided as part of the meta transaction message. Since we expect wallet to parse the message for proper display, they can handle chainId verification too.

The chainId is then passed in to the call and checked against the current chain's chainId. This is so a proper error message is given in case the chainId do not match.

Note that such system does not handle fork transition. EIP-1344 is not well suited for it as it would require the maintenance of a chainId cache to handle past chainId and this would add extra complexity. Because contentious chain forks are infrequent and the only downside would be that user would have to resubmit their signed message for the new fork they are interested in, the complexity added is not worth it. Plus even a cache is not full proof as mentioned in EIP-1965

On the other hand if EIP-1965 was implemented we could have used it to offer safe fork transition too.

Batching Capability

The Forwarder allows Meta transaction to be batched in a single transaction, but instead of complexifying the message format to include an array of calls, the implementation instead relies on a special batch function that can be the target of the meta transaction itself.

Wallet will need to parse this special call to display the list of meta transaction call to be executed.

Backwards Compatibility

This is a new contract and interface, no backward compatibility issues

Test Cases

Tests can be found here

Implementation

/* EIP-2585 Minimal Native Meta Transaction Forwarder
 * This standard defines a universal native meta transaction smart contract
 * that accept specially crafted Externally Owned Accounts (EOA) signed message
 * to forward to receiver contract via third parties.
 *
 * Written in 2020 by Ronan Sandford
 *
 * To the extent possible under law, the author(s) have dedicated all copyright
 * and related and neighboring rights to this software to the public domain
 * worldwide. This software is distributed without any warranty.
 *
 * You should have received a copy of the CC0 Public Domain Dedication along
 * with this software. If not, see
 * <https://creativecommons.org/publicdomain/zero/1.0/>.
 *    
 *       .-''-.  .-./`) .-------.               .`````-.  ,--------.     .-''''-.  ,--------.   
 *     .'_ _   \ \ .-.')\  _(`)_ \             /   ,-.  \ |   _____|    /  _--.  \ |   _____|   
 *    / ( ` )   '/ `-' \| (_ o._)|            (___/  |   ||  )          |_( )_ ' | |  )         
 *   . (_ o _)  | `-'`"`|  (_,_) /_ _    _ _        .'  / |  '----.     (_ o _). / |  '----.    
 *   |  (_,_)___| .---. |   '-.-'( ' )--( ' )   _.-'_.-'  |_.._ _  '.  .'(_,_).  `.|_.._ _  '.  
 *   '  \   .---. |   | |   |   (_{;}_)(_{;}_)_/_  .'        ( ' )   \|_( )_    \  |  ( ' )   \ 
 *    \  `-'    / |   | |   |    (_,_)--(_,_)( ' )(__..--. _(_{;}_)  |(_ o _)   /  |_(_{;}_)  | 
 *     \       /  |   | /   )               (_{;}_)      ||  (_,_)  /  (_,_)..-' .'|  (_,_)  /  
 *      `'-..-'   '---' `---'                (_,_)-------' `...__..'     `-....-'   `...__..'   
 *                                                                                           
 */
pragma solidity 0.6.4;
pragma experimental ABIEncoderV2;

interface ERC1271 {
    function isValidSignature(bytes calldata data, bytes calldata signature) external view returns (bytes4 magicValue);
}

interface ERC1654 {
   function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue);
}

interface ReplayProtection {
    function checkAndUpdateNonce(address signer, bytes calldata nonce) external returns (bool);
}

interface Forwarder {

    enum SignatureType { DIRECT, EIP1654, EIP1271 }

    struct Message {
        address from;
        address to;
        uint256 chainId;
        address replayProtection;
        bytes nonce;
        bytes data;
        bytes32 innerMessageHash;
	}

    function forward(
        Message calldata message,
        SignatureType signatureType,
        bytes calldata signature
    ) external payable;
}

library SigUtil {
    function recover(bytes32 hash, bytes memory sig) internal pure returns (address recovered) {
        require(sig.length == 65, "SIGNATURE_INVALID_LENGTH");

        bytes32 r;
        bytes32 s;
        uint8 v;
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }

        // Version of signature should be 27 or 28, but 0 and 1 are also possible versions
        if (v < 27) {
            v += 27;
        }
        require(v == 27 || v == 28, "SIGNATURE_INVALID_V");

        recovered = ecrecover(hash, v, r, s);
        require(recovered != address(0), "SIGNATURE_ZERO_ADDRESS");
    }

    function eth_sign_prefix(bytes32 hash) internal pure returns (bytes memory) {
        return abi.encodePacked("\x19Ethereum Signed Message:\n32", hash);
    }
}

/// @notice Forwarder for Meta Transactions Using EIP712 Signing Standard, also implement default Replay Protection using 2 dimensional nonces
contract EIP712Forwarder is Forwarder, ReplayProtection {

    // ///////////////////////////// FORWARDING EOA META TRANSACTION ///////////////////////////////////

    bytes4 internal constant ERC1271_MAGICVALUE = 0x20c13b0b;
    bytes4 internal constant ERC1654_MAGICVALUE = 0x1626ba7e;

    /// @notice forward call from EOA signed message
    /// @param message.from address from which the message come from (For EOA this is the same as signer)
    /// @param message.to target of the call
    /// @param message.replayProtection contract address that check and update nonce
    /// @param message.nonce nonce value
    /// @param message.data call data
    /// @param message.innerMessageHash extra data hashed that can be used as embedded message for implementing more complex scenario, with one sig
    /// @param signatureType signatureType either EOA, EIP1271 or EIP1654
    /// @param signature signature
    function forward(
        Message memory message,
        SignatureType signatureType,
        bytes memory signature
    ) public override payable { // external with ABIEncoderV2 Struct is not supported in solidity < 0.6.4
        require(_isValidChainId(message.chainId), "INVALID_CHAIN_ID");
        _checkSigner(message, signatureType, signature);
        // optimization to avoid call if using default nonce strategy
        // this contract implements a default nonce strategy and can be called directly
        if (message.replayProtection == address(0) || message.replayProtection == address(this)) {
            require(checkAndUpdateNonce(message.from, message.nonce), "NONCE_INVALID");
        } else {
            require(ReplayProtection(message.replayProtection).checkAndUpdateNonce(message.from, message.nonce), "NONCE_INVALID");
        }

        _call(message.from, message.to, msg.value, message.data);
    }


    // /////////////////////////////////// BATCH CALL /////////////////////////////////////

    struct Call {
        address to;
        bytes data;
        uint256 value;
    }

    /// @notice batcher function that can be called as part of a meta transaction (allowing to batch call atomically)
    /// @param calls list of call data and destination
    function batch(Call[] memory calls) public payable { // external with ABIEncoderV2 Struct is not supported in solidity < 0.6.4
        require(msg.sender == address(this), "FORWARDER_ONLY");
        address signer;
        bytes memory data = msg.data;
        uint256 length = msg.data.length;
        assembly { signer := and(mload(sub(add(data, length), 0x00)), 0xffffffffffffffffffffffffffffffffffffffff) }
        for(uint256 i = 0; i < calls.length; i++) {
            _call(signer, calls[i].to, calls[i].value, calls[i].data);
        }
    }

    // /////////////////////////////////// REPLAY PROTECTION /////////////////////////////////////

    mapping(address => mapping(uint128 => uint128)) _batches;

    /// @notice implement a default nonce stategy
    /// @param signer address to check and update nonce for
    /// @param nonce value of nonce sent as part of the forward call
    function checkAndUpdateNonce(address signer, bytes memory nonce) public override returns (bool) {
        // TODO? default nonce strategy could be different (maybe the most versatile : batchId + Nonce)
        uint256 value = abi.decode(nonce, (uint256));
        uint128 batchId = uint128(value / 2**128);
        uint128 batchNonce = uint128(value % 2**128);

        uint128 currentNonce = _batches[signer][batchId];
        if (batchNonce == currentNonce) {
            _batches[signer][batchId] = currentNonce + 1;
            return true;
        }
        return false;
    }

    function getNonce(address signer, uint128 batchId) external view returns (uint128) {
        return _batches[signer][batchId];
    }


    // ///////////////////////////////// INTERNAL ////////////////////////////////////////////

    function _call(
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        (bool success,) = to.call.value(value)(abi.encodePacked(data, from));
        if (!success) {
            assembly {
                let returnDataSize := returndatasize()
                returndatacopy(0, 0, returnDataSize)
                revert(0, returnDataSize)
            }
        }
    }

    function _checkSigner(
        Message memory message,
        SignatureType signatureType,
        bytes memory signature
    ) internal view returns (address) {
        bytes memory dataToHash = _encodeMessage(message);
        if (signatureType == SignatureType.EIP1271) {
            require(ERC1271(message.from).isValidSignature(dataToHash, signature) == ERC1271_MAGICVALUE, "SIGNATURE_1271_INVALID");
        } else if(signatureType == SignatureType.EIP1654){
            require(ERC1654(message.from).isValidSignature(keccak256(dataToHash), signature) == ERC1654_MAGICVALUE, "SIGNATURE_1654_INVALID");
        } else {
            address signer = SigUtil.recover(keccak256(dataToHash), signature);
            require(signer == message.from, "SIGNATURE_WRONG_SIGNER");
        }
    }

    function _isValidChainId(uint256 chainId) internal view returns (bool) {
        uint256 _chainId;
        assembly {_chainId := chainid() }
        return chainId == _chainId;
    }

    bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256(
        "EIP712Domain(string name,string version)"
    );
    bytes32 constant DOMAIN_SEPARATOR = keccak256(
        abi.encode(
            EIP712DOMAIN_TYPEHASH,
            keccak256("Forwarder"),
            keccak256("1")
        )
    );

    bytes32 constant METATRANSACTION_TYPEHASH = keccak256(
        "MetaTransaction(address from,address to,uint256 value,uint256 chainId,address replayProtection,bytes nonce,bytes data,bytes32 innerMessageHash)"
    );

    function _encodeMessage(Message memory message) internal view returns (bytes memory) {
        return abi.encodePacked(
            "\x19\x01",
            DOMAIN_SEPARATOR,
            keccak256(abi.encode(
                METATRANSACTION_TYPEHASH,
                message.from,
                message.to,
                msg.value,
                message.chainId,
                message.replayProtection,
                keccak256(message.nonce),
                keccak256(message.data),
                message.innerMessageHash
            ))
        );
    }
}

Security Considerations

The Security of the proposal relies on the safety of the contract implementation only. One of the reason, native meta transaction has not taken off is that contract creator have to trust a specific mechanism. The proposal aims to be a minimal meta transaction implementation.

References

Copyright

Copyright and related rights waived via CC0.

@wighawag wighawag changed the title Minimal MetaTransaction Forwarder Minimal And Extensible Meta Transaction Forwarder Apr 5, 2020
@danfinlay
Copy link
Contributor

I think this is a wonderful synthesis of many of the best ideas that I saw on display during the MetaMask MetaTransaction Hackathon, and I applaud it.

Having custom replay-protection is a huge addition to 1776, and pointing to a custom replay protection contract means it's infinitely extensible. A few ideas:

  • Dependent transactions can be queued
  • Most transactions can be parallelized
  • Transaction "options" can be handed out that can be submitted at any time, effectively enabling off-chain capabilities.

It seems like this can be deployed whenever people find it trustworthy (after an audit?), and then contracts can begin integrating compatibility, so it seems to have a very smooth adoption path.

@Amxx
Copy link
Contributor

Amxx commented Apr 15, 2020

I believe
ERC1271_MAGICVALUE and ERC1654_MAGICVALUE
could be replaced with
ERC1271(0).isValidSignature.selector and ERC1654(0).isValidSignature.selector

@Amxx
Copy link
Contributor

Amxx commented Apr 15, 2020

couldn't _call be done is vanilia solidity with

(bool success, bytes memory returndata) = to.call{value: value}(abi.encodePacked(data, from));
require(success, string(returndata));

That is more readeable

@Amxx
Copy link
Contributor

Amxx commented Apr 15, 2020

bytes32 constant EIP712DOMAIN_TYPEHASH
bytes32 constant DOMAIN_SEPARATOR
bytes32 constant METATRANSACTION_TYPEHASH

Should be hardcoded bytes32, with comment showing the receipe. Unlike what you expect, the current implementation is not computed at compile time and thus optimised.

@wighawag
Copy link
Contributor Author

Hi @Amxx thanks for the suggestions. Going to update the code. Probably better to put this low details comment there though : https://github.com/ethereum/EIPs/pull/2600/files so we keep that thread on the high level matters

Re vanilia solidity call, the issue is that require(success, string(returndata)); will wrap the error string into another Error(string)

@Amxx
Copy link
Contributor

Amxx commented Apr 16, 2020

Any reason why gas amount value for the forward call is not specified in the message ?

@wighawag
Copy link
Contributor Author

This is because this is unnecessary as the forwarder does not deal with fee repayment.
the whole gas is passed and if it error due to a lack of gas the whole call fails.

gas fee repayment can be implemented on top though, in which case the gas to be passed will be passed to the forwarder. See EIP-1776 implementation here : https://github.com/wighawag/eip-2585/blob/ee20b96ba284e45be09c01adec76c21b98dfd4bb/contracts/src/EIP1776ForwarderWrapper.sol#L108

@vincentlg
Copy link

vincentlg commented Apr 17, 2020

Thank you for this initiative 👏
All this work is absolutely necessary to standardize the way of relaying meta-transactions.
We are thinking about using it for our relay, it meets 90% of our needs. We already have some feedback / questions.

About innerMessageHash
I understand the benefits of delegating the logic of refund to the Dapp but this has some drawbacks in my opinion.
At first glance, this mechanism is not easy to understand. Fortunately the example of implementation in EIP1776 allowed us to see how to use the innerMessageHash for gas refund. It is a very flexible mechanism but we can consider that this challenge may be resolved by the relay. Can you give other use cases for innerMessageHash (other than gas refund) ? Are there any other benefits to innerMessageHash than avoiding having two messages, one for the forwarder and one for metaTx mechanism ?

About the replay protection
The use of batchId and batchNonce to explain the multidimensional nonce was little confusing. The term batch may suggest something with an end, with a limited number of transactions.
But in this case, it’s more like a channel or a slot that you can use to send concurrent transactions.
Furthermore, Batch is also used for batch execution via a transaction array. Are these two notions linked?

@Nipol
Copy link

Nipol commented May 14, 2020

I'm not sure if SignatureType is a good way to distinguish EIP-1271(#1271) and EIP-1654(#1654), or if EIP-165(#165) is the right way to check the interface.
I think this simply distinguishes which interface to use.
However, Wallet service providers such as Gnosis Safe, Argent, and Authereum do not implement EIP-165. 😢

Example-implemented, SignatureType is subtly out of the question for this EIP.
Relayer must deduce SignatureType in Off-chain. I think these specifications should be fully validated in on-chain.

@wighawag
Copy link
Contributor Author

Hi @vincentlg and @Nipol thanks for your comments and sorry for the delay

@vincentlg I agree that batchId and BatchNonce are misleading
re innerMessageHash, this gives flexibility to the forwarder that can support its own EIP-712 format

But I have actually started to think of a different way to achieve full forwarder flexibility. I show cased it in my latest ETHGlobal hackathon : https://github.com/wighawag/gsn-playground/blob/master/contracts/src/MetaTransaction/ForwarderRegistry.sol

This uses a permission-less registry and forwarder can thus use any signature scheme they want, not necessarely EIP-712. Plus if we come up with a minimal metatx standard for forwarder, the registrry use is up to the receiver.

@Nipol I think the signature type can be safely guessed off-chain. What would be the benefit of figuring it out on-chain ? This could also be part of the EIP-712 message parameters so the relayer would be forced to pass the intended value

@yashnaman
Copy link

Is this forwarder on mainnet?
If not is there similar forwarder that uses different standard and is already on mainnet?

@wighawag
Copy link
Contributor Author

wighawag commented Oct 5, 2020

@yashnaman as far as I know there is no deployment of this standard yet.
It is also worth noting that the contract code published there is still work in progress

As for other standards

there is a minimal standard (compatible with EIP-2585) here : https://eips.ethereum.org/EIPS/eip-2771

If your receiver contract support it, it will be compatible with EIP-2585 or other standard, like GSN v2: #2770

@github-actions
Copy link

github-actions bot commented Nov 7, 2021

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Nov 7, 2021
@github-actions
Copy link

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants