datahaven/operator/precompiles/call-permit/README.md
Steve Degosserie 479af2e192
feat: Add Moonbeam CallPermit precompile (#140)
## 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>
2025-09-07 15:00:37 +02:00

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));
  }
);