// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
pragma experimental ABIEncoderV2;
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol";
import "./ERC20ForwardRequestTypes.sol";
import "./libs/Ownable.sol";
/**
*
* @title BiconomyForwarder
*
* @notice A trusted forwarder for Biconomy relayed meta transactions
* @notice You should verify your contracts on all chains they are deployed on
*
* @dev - Inherits the ERC20ForwarderRequest struct
* @dev - Verifies EIP712 signatures
* @dev - Verifies personalSign signatures
* @dev - Implements 2D nonces... each Tx has a BatchId and a BatchNonce
* @dev - Keeps track of highest BatchId used by a given address, to assist in encoding of transactions client-side
* @dev - maintains a list of verified domain seperators
*
*/
contract BiconomyForwarder is ERC20ForwardRequestTypes, Ownable {
using ECDSA for bytes32;
mapping(bytes32 => bool) public domains;
uint256 chainId;
string public constant EIP712_DOMAIN_TYPE =
"EIP712Domain(string name,string version,address verifyingContract,bytes32 salt)";
bytes32 public constant REQUEST_TYPEHASH =
keccak256(
bytes(
"ERC20ForwardRequest(address from,address to,address token,uint256 txGas,uint256 tokenGasPrice,uint256 batchId,uint256 batchNonce,uint256 deadline,bytes data)"
)
);
mapping(address => mapping(uint256 => uint256)) nonces;
constructor(address _owner) public Ownable(_owner) {
uint256 id;
assembly {
id := chainid()
}
chainId = id;
require(_owner != address(0), "Owner Address cannot be 0");
}
/**
* @dev registers domain seperators, maintaining that all domain seperators used for EIP712 forward requests use...
* ... the address of this contract and the chainId of the chain this contract is deployed to
* @param name : name of dApp/dApp fee proxy
* @param version : version of dApp/dApp fee proxy
*/
function registerDomainSeparator(
string calldata name,
string calldata version
) external onlyOwner {
uint256 id;
/* solhint-disable-next-line no-inline-assembly */
assembly {
id := chainid()
}
bytes memory domainValue = abi.encode(
keccak256(bytes(EIP712_DOMAIN_TYPE)),
keccak256(bytes(name)),
keccak256(bytes(version)),
address(this),
bytes32(id)
);
bytes32 domainHash = keccak256(domainValue);
domains[domainHash] = true;
emit DomainRegistered(domainHash, domainValue);
}
event DomainRegistered(bytes32 indexed domainSeparator, bytes domainValue);
/**
* @dev returns a value from the nonces 2d mapping
* @param from : the user address
* @param batchId : the key of the user's batch being queried
* @return nonce : the number of transaction made within said batch
*/
function getNonce(address from, uint256 batchId)
public
view
returns (uint256)
{
return nonces[from][batchId];
}
/**
* @dev an external function which exposes the internal _verifySigEIP712 method
* @param req : request being verified
* @param domainSeparator : the domain separator presented to the user when signing
* @param sig : the signature generated by the user's wallet
*/
function verifyEIP712(
ERC20ForwardRequest calldata req,
bytes32 domainSeparator,
bytes calldata sig
) external view {
_verifySigEIP712(req, domainSeparator, sig);
}
/**
* @dev verifies the call is valid by calling _verifySigEIP712
* @dev executes the forwarded call if valid
* @dev updates the nonce after
* @param req : request being executed
* @param domainSeparator : the domain separator presented to the user when signing
* @param sig : the signature generated by the user's wallet
* @return success : false if call fails. true otherwise
* @return ret : any return data from the call
*/
function executeEIP712(
ERC20ForwardRequest calldata req,
bytes32 domainSeparator,
bytes calldata sig
) external returns (bool success, bytes memory ret) {
_verifySigEIP712(req, domainSeparator, sig);
_updateNonce(req);
/* solhint-disable-next-line avoid-low-level-calls */
(success, ret) = req.to.call{gas: req.txGas}(
abi.encodePacked(req.data, req.from)
);
// Validate that the relayer has sent enough gas for the call.
// See https://ronan.eth.link/blog/ethereum-gas-dangers/
assert(gasleft() > req.txGas / 63);
_verifyCallResult(
success,
ret,
"Forwarded call to destination did not succeed"
);
}
/**
* @dev an external function which exposes the internal _verifySigPersonSign method
* @param req : request being verified
* @param sig : the signature generated by the user's wallet
*/
function verifyPersonalSign(
ERC20ForwardRequest calldata req,
bytes calldata sig
) external view {
_verifySigPersonalSign(req, sig);
}
/**
* @dev verifies the call is valid by calling _verifySigPersonalSign
* @dev executes the forwarded call if valid
* @dev updates the nonce after
* @param req : request being executed
* @param sig : the signature generated by the user's wallet
* @return success : false if call fails. true otherwise
* @return ret : any return data from the call
*/
function executePersonalSign(
ERC20ForwardRequest calldata req,
bytes calldata sig
) external returns (bool success, bytes memory ret) {
_verifySigPersonalSign(req, sig);
_updateNonce(req);
(success, ret) = req.to.call{gas: req.txGas}(
abi.encodePacked(req.data, req.from)
);
// Validate that the relayer has sent enough gas for the call.
// See https://ronan.eth.link/blog/ethereum-gas-dangers/
assert(gasleft() > req.txGas / 63);
_verifyCallResult(
success,
ret,
"Forwarded call to destination did not succeed"
);
}
/**
* @dev Increments the nonce of given user/batch pair
* @dev Updates the highestBatchId of the given user if the request's batchId > current highest
* @dev only intended to be called post call execution
* @param req : request that was executed
*/
function _updateNonce(ERC20ForwardRequest calldata req) internal {
nonces[req.from][req.batchId]++;
}
/**
* @dev verifies the domain separator used has been registered via registerDomainSeparator()
* @dev recreates the 32 byte hash signed by the user's wallet (as per EIP712 specifications)
* @dev verifies the signature using Open Zeppelin's ECDSA library
* @dev signature valid if call doesn't throw
*
* @param req : request being executed
* @param domainSeparator : the domain separator presented to the user when signing
* @param sig : the signature generated by the user's wallet
*
*/
function _verifySigEIP712(
ERC20ForwardRequest calldata req,
bytes32 domainSeparator,
bytes memory sig
) internal view {
uint256 id;
/* solhint-disable-next-line no-inline-assembly */
assembly {
id := chainid()
}
require(
req.deadline == 0 || block.timestamp + 20 <= req.deadline,
"request expired"
);
require(domains[domainSeparator], "unregistered domain separator");
require(chainId == id, "potential replay attack on the fork");
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
domainSeparator,
keccak256(
abi.encode(
REQUEST_TYPEHASH,
req.from,
req.to,
req.token,
req.txGas,
req.tokenGasPrice,
req.batchId,
nonces[req.from][req.batchId],
req.deadline,
keccak256(req.data)
)
)
)
);
require(digest.recover(sig) == req.from, "signature mismatch");
}
/**
* @dev encodes a 32 byte data string (presumably a hash of encoded data) as per eth_sign
*
* @param hash : hash of encoded data that signed by user's wallet using eth_sign
* @return input hash encoded to matched what is signed by the user's key when using eth_sign*/
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
);
}
/**
* @dev recreates the 32 byte hash signed by the user's wallet
* @dev verifies the signature using Open Zeppelin's ECDSA library
* @dev signature valid if call doesn't throw
*
* @param req : request being executed
* @param sig : the signature generated by the user's wallet
*
*/
function _verifySigPersonalSign(
ERC20ForwardRequest calldata req,
bytes memory sig
) internal view {
require(
req.deadline == 0 || block.timestamp + 20 <= req.deadline,
"request expired"
);
bytes32 digest = prefixed(
keccak256(
abi.encodePacked(
req.from,
req.to,
req.token,
req.txGas,
req.tokenGasPrice,
req.batchId,
nonces[req.from][req.batchId],
req.deadline,
keccak256(req.data)
)
)
);
require(digest.recover(sig) == req.from, "signature mismatch");
}
/**
* @dev verifies the call result and bubbles up revert reason for failed calls
*
* @param success : outcome of forwarded call
* @param returndata : returned data from the frowarded call
* @param errorMessage : fallback error message to show
*/
function _verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) private pure {
if (!success) {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
// Check the signature length
if (signature.length != 65) {
revert("ECDSA: invalid signature length");
}
// Divide the signature in r, s and v variables
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solhint-disable-next-line no-inline-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* replicates the behavior of the
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
* JSON-RPC method.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
pragma experimental ABIEncoderV2;
/* deadline can be removed : GSN reference https://github.com/opengsn/gsn/blob/master/contracts/forwarder/IForwarder.sol (Saves 250 more gas)*/
/**
* This contract defines a struct which both ERC20FeeProxy and BiconomyForwarder inherit. ERC20ForwardRequest specifies all the fields present in the GSN V2 ForwardRequest struct,
* but adds the following :
* address token
* uint256 tokenGasPrice
* uint256 txGas
* uint256 batchNonce (can be removed)
* uint256 deadline
* Fields are placed in type order, to minimise storage used when executing transactions.
*/
contract ERC20ForwardRequestTypes {
/*allow the EVM to optimize for this,
ensure that you try to order your storage variables and struct members such that they can be packed tightly*/
struct ERC20ForwardRequest {
address from;
address to;
address token;
uint256 txGas;
uint256 tokenGasPrice;
uint256 batchId;
uint256 batchNonce;
uint256 deadline;
bytes data;
}
struct PermitRequest {
address holder;
address spender;
uint256 value;
uint256 nonce;
uint256 expiry;
bool allowed;
uint8 v;
bytes32 r;
bytes32 s;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor(address owner) public {
_owner = owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(
isOwner(),
"Only contract owner is allowed to perform this operation"
);
_;
}
/**
* @return the address of the owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
{
"compilationTarget": {
"BiconomyForwarder.sol": "BiconomyForwarder"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"domainSeparator","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"domainValue","type":"bytes"}],"name":"DomainRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"EIP712_DOMAIN_TYPE","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"domains","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ERC20ForwardRequestTypes.ERC20ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes32","name":"domainSeparator","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"executeEIP712","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ERC20ForwardRequestTypes.ERC20ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"executePersonalSign","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"batchId","type":"uint256"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"}],"name":"registerDomainSeparator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ERC20ForwardRequestTypes.ERC20ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes32","name":"domainSeparator","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"verifyEIP712","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ERC20ForwardRequestTypes.ERC20ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"verifyPersonalSign","outputs":[],"stateMutability":"view","type":"function"}]