Created
January 5, 2024 12:35
-
-
Save sulram/46578c97c51857b3e0e5dd92363ca1d6 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
const ethers = require("ethers"); | |
const { | |
DefenderRelaySigner, | |
DefenderRelayProvider, | |
} = require("defender-relay-client/lib/ethers"); | |
const speed = "safeLow"; | |
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, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment