-
-
Save spalladino/7fb3533e36e9b9a833f8e5c568c86815 to your computer and use it in GitHub Desktop.
Example application for sending EIP2771 meta-txs through a Defender Relayer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Sample recipient contract for the application. | |
// Refer to the https://github.com/opengsn/forwarder repo for more info. | |
pragma solidity ^0.6.8; | |
import "./gsnv2/BaseRelayRecipient.sol"; | |
contract Boxes is BaseRelayRecipient { | |
mapping(address => uint256) values; | |
event ValueSet(address who, uint256 value); | |
constructor(address _trustedForwarder) public { | |
trustedForwarder = _trustedForwarder; | |
} | |
function getValue(address who) public view returns (uint256) { | |
return values[who]; | |
} | |
function setValue(uint256 value) public { | |
address who = _msgSender(); | |
values[who] = value; | |
emit ValueSet(who, value); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// User client-side code for signing a meta-tx request | |
// Note that, instead of sending the tx, the end-user signs the request and sends it to a relayer server | |
// This server will process the request, and if valid, send the tx via a Defender Relayer | |
import ethers from 'ethers'; | |
import BoxesAbi from '../abis/Boxes.json'; | |
import ForwarderAbi from '../abis/Forwarder.json'; | |
const ZeroAddress = '0x0000000000000000000000000000000000000000'; | |
const BoxesAddress = process.env.REACT_APP_BOXES_ADDRESS || ZeroAddress; | |
const ForwarderAddress = process.env.REACT_APP_FORWARDER_ADDRESS || ZeroAddress; | |
const RelayUrl = process.env.REACT_APP_RELAY_URL || '/relay'; | |
const EIP712DomainType = [ | |
{ name: 'name', type: 'string' }, | |
{ name: 'version', type: 'string' }, | |
{ name: 'chainId', type: 'uint256' }, | |
{ name: 'verifyingContract', type: 'address' } | |
] | |
const ForwardRequestType = [ | |
{ name: 'from', type: 'address' }, | |
{ name: 'to', type: 'address' }, | |
{ name: 'value', type: 'uint256' }, | |
{ name: 'gas', type: 'uint256' }, | |
{ name: 'nonce', type: 'uint256' }, | |
{ name: 'data', type: 'bytes' } | |
] | |
const TypedData = { | |
domain: { | |
name: 'Defender', | |
version: '1', | |
chainId: 4, | |
verifyingContract: ForwarderAddress, | |
}, | |
primaryType: 'ForwardRequest', | |
types: { | |
EIP712Domain: EIP712DomainType, | |
ForwardRequest: ForwardRequestType | |
}, | |
message: {} | |
}; | |
export async function submit(number) { | |
// Initialize provider and signer from metamask | |
await window.ethereum.enable(); | |
const provider = new ethers.providers.Web3Provider(window.ethereum); | |
const signer = provider.getSigner(); | |
const from = await signer.getAddress(); | |
const network = await provider.getNetwork(); | |
if (network.chainId !== 4) throw new Error(`Must be connected to Rinkeby`); | |
// Get nonce for current signer | |
const forwarder = new ethers.Contract(ForwarderAddress, ForwarderAbi, provider); | |
const nonce = await forwarder.getNonce(from).then(nonce => nonce.toString()); | |
// Encode meta-tx request | |
const boxesInterface = new ethers.utils.Interface(BoxesAbi); | |
const data = boxesInterface.encodeFunctionData('setValue', [number]); | |
const request = { | |
from, | |
to: BoxesAddress, | |
value: 0, | |
gas: 1e6, | |
nonce, | |
data | |
}; | |
const toSign = { ...TypedData, message: request }; | |
// Directly call the JSON RPC interface, since ethers does not support signTypedDataV4 yet | |
// See https://github.com/ethers-io/ethers.js/issues/830 | |
const signature = await provider.send('eth_signTypedData_v4', [from, JSON.stringify(toSign)]); | |
// Send request to the server | |
const response = await fetch(RelayUrl, { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ ...request, signature }) | |
}).then(r => r.json()); | |
return response; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Server-side code for receiving meta-tx requests | |
// The server validates the request, and if accepted, will send the meta-tx via a Defender Relayer | |
const RelayerApiKey = process.env.APP_API_KEY; | |
const RelayerSecretKey = process.env.APP_SECRET_KEY; | |
const ForwarderAddress = process.env.APP_FORWARDER_ADDRESS; | |
const { Relayer } = require('defender-relay-client'); | |
const { ethers } = require('ethers'); | |
const ForwarderAbi = require('../artifacts/Forwarder.json').abi; | |
const { TypedDataUtils } = require('eth-sig-util'); | |
const { bufferToHex } = require('ethereumjs-util'); | |
const EIP712DomainType = [ | |
{ name: 'name', type: 'string' }, | |
{ name: 'version', type: 'string' }, | |
{ name: 'chainId', type: 'uint256' }, | |
{ name: 'verifyingContract', type: 'address' } | |
] | |
const ForwardRequestType = [ | |
{ name: 'from', type: 'address' }, | |
{ name: 'to', type: 'address' }, | |
{ name: 'value', type: 'uint256' }, | |
{ name: 'gas', type: 'uint256' }, | |
{ name: 'nonce', type: 'uint256' }, | |
{ name: 'data', type: 'bytes' } | |
]; | |
const TypedData = { | |
domain: { | |
name: 'Defender', | |
version: '1', | |
chainId: 4, | |
verifyingContract: ForwarderAddress | |
}, | |
primaryType: 'ForwardRequest', | |
types: { | |
EIP712Domain: EIP712DomainType, | |
ForwardRequest: ForwardRequestType | |
}, | |
message: {} | |
}; | |
const GenericParams = 'address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data'; | |
const TypeName = `ForwardRequest(${GenericParams})`; | |
const TypeHash = ethers.utils.id(TypeName); | |
const DomainSeparator = bufferToHex(TypedDataUtils.hashStruct('EIP712Domain', TypedData.domain, TypedData.types)); | |
const SuffixData = '0x'; | |
async function relay(request) { | |
// Unpack request | |
const { to, from, value, gas, nonce, data, signature } = request; | |
// Validate request | |
const provider = new ethers.providers.InfuraProvider('rinkeby', process.env.APP_INFURA_KEY); | |
const forwarder = new ethers.Contract(ForwarderAddress, ForwarderAbi, provider); | |
const args = [ | |
{ to, from, value, gas, nonce, data }, | |
DomainSeparator, | |
TypeHash, | |
SuffixData, | |
signature | |
]; | |
await forwarder.verify(...args); | |
// Send meta-tx through Defender | |
const forwardData = forwarder.interface.encodeFunctionData('execute', args); | |
const relayer = new Relayer(RelayerApiKey, RelayerSecretKey); | |
const tx = await relayer.sendTransaction({ | |
speed: 'fast', | |
to: ForwarderAddress, | |
gasLimit: gas, | |
data: forwardData, | |
}); | |
console.log(`Sent meta-tx: ${tx.hash}`); | |
return tx; | |
} | |
// Handler for lambda function | |
exports.handler = async function(event, context, callback) { | |
try { | |
const data = JSON.parse(event.body); | |
const response = await relay(data); | |
callback(null, { statusCode: 200, body: JSON.stringify(response) }); | |
} catch (err) { | |
callback(err); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment