## Summary This PR adds Moonbeam's CallPermit precompile to DataHaven, enabling gasless meta-transactions through EIP-712 signature-based permissions. Users can sign transaction permits offline, allowing relayers to execute transactions on their behalf while maintaining full security and authentication. ## Key Features ### CallPermit Precompile Functions - **`dispatch(address from, address to, uint256 value, bytes data, uint64[] gasLimit, uint256 deadline, uint8 v, bytes32 r, bytes32 s)`**: Execute permitted calls with signature verification - **`nonces(address owner)`**: Get current nonce for permit validation ### Technical Implementation - **Address**: `0x080A` (2058 in decimal) - **EIP-712 Compliance**: Structured signature validation with proper domain separation - **Nonce Management**: Per-user nonce tracking for replay protection - **Deadline Validation**: Time-bound permits for enhanced security - **Gas Forwarding**: Proper gas limit enforcement and forwarding Depends on https://github.com/datahaven-xyz/datahaven/pull/137 --------- Co-authored-by: Claude <noreply@anthropic.com>
2.8 KiB
Call Permit Precompile
This precompile aims to be a general-purpose tool to perform gas-less transactions.
It allows a user (we'll call her Alice) to sign a call permit with MetaMask (using the EIP712 standard), which can then be dispatched by another user (we'll call him Bob) with a transaction.
Bob can make a transaction to the Call Permit Precompile with the call data and Alice's signature. If the permit and signature are valid, the precompile will perform the call on the behalf of Alice, as if Alice made a transaction herself. Bob is thus paying the transaction fees and Alice can perform a call without having any native currency to pay for fees (she'll still need to have some if the call includes a transfer).
How to sign the permit
The following code is an example that is working in a Metamask-injected webpage. Bob then need to make a transaction towards the precompile address with the same data and Alice's signature.
await window.ethereum.enable();
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
const from = accounts[0];
const to = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const value = 42;
const data = "0xdeadbeef";
const gaslimit = 100000;
const nonce = 0;
const deadline = 1000;
const createPermitMessageData = function () {
const message = {
from: from,
to: to,
value: value,
data: data,
gaslimit: gaslimit,
nonce: nonce,
deadline: deadline,
};
const typedData = JSON.stringify({
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
CallPermit: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "data", type: "bytes" },
{ name: "gaslimit", type: "uint64" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
},
primaryType: "CallPermit",
domain: {
name: "Call Permit Precompile",
version: "1",
chainId: 0,
verifyingContract: "0x000000000000000000000000000000000000080a",
},
message: message,
});
return {
typedData,
message,
};
};
const method = "eth_signTypedData_v4";
const messageData = createPermitMessageData();
const params = [from, messageData.typedData];
web3.currentProvider.sendAsync(
{
method,
params,
from,
},
function (err, result) {
if (err) return console.dir(err);
if (result.error) {
alert(result.error.message);
return console.error("ERROR", result);
}
console.log("Signature:" + JSON.stringify(result.result));
}
);