Skip to content

Instantly share code, notes, and snippets.

@sulram
Created January 5, 2024 12:35
Show Gist options
  • Save sulram/46578c97c51857b3e0e5dd92363ca1d6 to your computer and use it in GitHub Desktop.
Save sulram/46578c97c51857b3e0e5dd92363ca1d6 to your computer and use it in GitHub Desktop.
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