const ethers = require("ethers"); const { DefenderRelaySigner, DefenderRelayProvider, } = require("defender-relay-client/lib/ethers"); const speed = "fastest"; const ForwarderAbi = [ { inputs: [], stateMutability: "nonpayable", type: "constructor" }, { inputs: [ { components: [ { internalType: "address", name: "from", type: "address" }, { internalType: "address", name: "to", type: "address" }, { internalType: "uint256", name: "value", type: "uint256" }, { internalType: "uint256", name: "gas", type: "uint256" }, { internalType: "uint256", name: "nonce", type: "uint256" }, { internalType: "bytes", name: "data", type: "bytes" }, ], internalType: "struct MinimalForwarder.ForwardRequest", name: "req", type: "tuple", }, { internalType: "bytes", name: "signature", type: "bytes" }, ], name: "execute", outputs: [ { internalType: "bool", name: "", type: "bool" }, { internalType: "bytes", name: "", type: "bytes" }, ], stateMutability: "payable", type: "function", }, { inputs: [{ internalType: "address", name: "from", type: "address" }], name: "getNonce", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [ { components: [ { internalType: "address", name: "from", type: "address" }, { internalType: "address", name: "to", type: "address" }, { internalType: "uint256", name: "value", type: "uint256" }, { internalType: "uint256", name: "gas", type: "uint256" }, { internalType: "uint256", name: "nonce", type: "uint256" }, { internalType: "bytes", name: "data", type: "bytes" }, ], internalType: "struct MinimalForwarder.ForwardRequest", name: "req", type: "tuple", }, { internalType: "bytes", name: "signature", type: "bytes" }, ], name: "verify", outputs: [{ internalType: "bool", name: "", type: "bool" }], stateMutability: "view", type: "function", }, ]; const erc20PermitAbi = [ { inputs: [ { internalType: "address", name: "owner", type: "address", }, { internalType: "address", name: "spender", type: "address", }, { internalType: "uint256", name: "value", type: "uint256", }, { internalType: "uint256", name: "deadline", type: "uint256", }, { internalType: "uint8", name: "v", type: "uint8", }, { internalType: "bytes32", name: "r", type: "bytes32", }, { internalType: "bytes32", name: "s", type: "bytes32", }, ], name: "permit", outputs: [], stateMutability: "nonpayable", type: "function", }, ]; async function relayGeneric(forwarder, request, signature) { // Validate request on the forwarder contract const valid = await forwarder.verify(request, signature); if (!valid) throw new Error(`Invalid request`); // Send meta-tx through relayer to the forwarder contract const gasLimit = (parseInt(request.gas) + 50000).toString(); return await forwarder.execute(request, signature, { gasLimit }); } async function relayTokenApproval( permitContract, permitMessage, permitSignature ) { // Tx args const { owner, spender, value, deadline, v, r, s } = permitMessage; // Send meta-tx through relayer to the forwarder contract return await permitContract.permit(owner, spender, value, deadline, v, r, s); } async function handler(event) { // Parse webhook payload if (!event.request || !event.request.body) throw new Error(`Missing payload`); const { type } = event.request.body; console.log("Type", type); // Initialize Relayer provider and signer, and forwarder contract const credentials = { ...event }; const provider = new DefenderRelayProvider(credentials); const signer = new DefenderRelaySigner(credentials, provider, { speed, }); let tx; if (type == "permit") { // ERC20 Permit const { request, signature } = event.request.body; // Initialize permitContract const permitContract = new ethers.Contract( request.to, erc20PermitAbi, signer ); tx = await relayTokenApproval(permitContract, request, signature); } else if (type == "forward") { // Gasless tx const { request, signature, forwarderAddress } = event.request.body; console.log(forwarderAddress); // Initialize forwarder contract const forwarder = new ethers.Contract( forwarderAddress, ForwarderAbi, signer ); console.log(`Relaying`, request); console.log(`Signature`, signature); // fix ledger live where signature result in v = 0, 1. const fixedSig = ethers.utils.joinSignature(ethers.utils.splitSignature(signature)); console.log(`Fixed Signature`, fixedSig); tx = await relayGeneric(forwarder, request, fixedSig); } else { throw new Error( `Invalid gasless transaction type. Provide type 'permit' or 'forwarder'.` ); } console.log(`Sent meta-tx: ${tx.hash}`); return { txHash: tx.hash, txResponse: tx }; } module.exports = { handler, };