Interagindo com o Call Permit Precompile¶
Introdução¶
O Call Permit Precompile permite que um usuário assine um permit EIP-712 para qualquer chamada EVM, que pode ser despachada por qualquer pessoa ou contrato inteligente. O despachante paga as taxas, viabilizando transações “gasless” para o signatário (desde que a chamada não envie valor).
O Call Permit Precompile está localizado no seguinte endereço:
0x0000000000000000000000000000000000000802
Note
O uso de precompiladas pode trazer consequências inesperadas. As precompiladas do Tanssi são derivadas das do Moonbeam; portanto, familiarize-se com as considerações de segurança das precompiladas do Moonbeam.
Interface Solidity¶
CallPermit.sol expõe três funções principais:
CallPermit.sol
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;
/// @dev The CallPermit contract's address.
address constant CALL_PERMIT_ADDRESS = 0x0000000000000000000000000000000000000802;
/// @dev The CallPermit contract's instance.
CallPermit constant CALL_PERMIT_CONTRACT = CallPermit(CALL_PERMIT_ADDRESS);
/// @author The Moonbeam Team
/// @title Call Permit Interface
/// @dev The interface aims to be a general-purpose tool to perform gas-less transactions. It uses the EIP-712 standard,
/// and signed messages can be dispatched by another network participant with a transaction
/// @custom:address 0x0000000000000000000000000000000000000802
interface CallPermit {
/// @dev Dispatch a call on the behalf of an other user with a EIP712 permit.
/// Will revert if the permit is not valid or if the dispatched call reverts or errors (such as
/// out of gas).
/// If successful the EIP712 nonce is increased to prevent this permit to be replayed.
/// @param from Who made the permit and want its call to be dispatched on their behalf.
/// @param to Which address the call is made to.
/// @param value Value being transferred from the "from" account.
/// @param data Call data
/// @param gaslimit Gaslimit the dispatched call requires.
/// Providing it prevents the dispatcher to manipulate the gaslimit.
/// @param deadline Deadline in UNIX seconds after which the permit will no longer be valid.
/// @param v V part of the signature.
/// @param r R part of the signature.
/// @param s S part of the signature.
/// @return output Output of the call.
/// @custom:selector b5ea0966
function dispatch(
address from,
address to,
uint256 value,
bytes memory data,
uint64 gaslimit,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (bytes memory output);
/// @dev Returns the current nonce for given owner.
/// A permit must have this nonce to be consumed, which will
/// increase the nonce by one.
/// @custom:selector 7ecebe00
function nonces(address owner) external view returns (uint256);
/// @dev Returns the EIP712 domain separator. It is used to avoid replay
/// attacks across assets or other similar EIP712 message structures.
/// @custom:selector 3644e515
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
- dispatch(from, to, value, data, gasLimit, deadline, v, r, s) — executa a chamada assinada; falha se o permit for inválido ou se a chamada revertir. Aumenta o nonce do signatário após sucesso.
- nonces(owner) — retorna o nonce atual de
owner. - DOMAIN_SEPARATOR() — retorna o separador de domínio EIP-712 usado para evitar replay.
DOMAIN_SEPARATOR e assinatura¶
O separador segue o EIP-712 com name = "Call Permit Precompile", version = "1", chainId da rede e o endereço do precompile como verifyingContract. A mensagem assinada inclui from, to, value, data, gasLimit, deadline e o nonce atual.
Preparar contratos de exemplo¶
Use o contrato SetMessage.sol para demonstrar:
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.7;
contract SetMessage {
string storedMessage;
function set(string calldata x) public {
storedMessage = x;
}
function get() public view returns (string memory) {
return storedMessage;
}
}
No Remix:
- Crie arquivos CallPermit.sol e SetMessage.sol.
- Compile ambos.
- Implemente SetMessage.sol normalmente.
- Acesse o Call Permit Precompile clicando em At Address e informando
0x0000000000000000000000000000000000000802.
Gerar a assinatura¶
Você pode gerar a assinatura com Ethers.js usando EIP-712:
import { ethers } from 'ethers';
const from = 'INSERT_FROM_ADDRESS';
const to = 'INSERT_TO_ADDRESS';
const value = 0;
const data =
'0x4ed3885e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000';
const gaslimit = 100000;
const nonce = 'INSERT_SIGNERS_NONCE';
const deadline = 'INSERT_DEADLINE';
const createPermitMessageData = () => {
const message = {
from: from,
to: to,
value: value,
data: data,
gaslimit: gaslimit,
nonce: nonce,
deadline: deadline,
};
const typedData = {
types: {
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: INSERT-CHAIN-ID,
verifyingContract: '0x0000000000000000000000000000000000000802',
},
message: message,
};
return {
typedData,
message,
};
};
const messageData = createPermitMessageData();
// For demo purposes only. Never store your private key in a JavaScript/TypeScript file
const privateKey = 'INSERT_PRIVATE_KEY';
const wallet = new ethers.Wallet(privateKey);
const signature = await wallet.signTypedData(messageData.typedData.domain, messageData.typedData.types, messageData.message);
console.log(`Transaction successful with hash: ${signature}`);
const ethersSignature = ethers.Signature.from(signature);
const formattedSignature = {
r: ethersSignature.r,
s: ethersSignature.s,
v: ethersSignature.v,
};
console.log(formattedSignature);
Preencha:
from: endereço que autoriza.to: endereço do contrato alvo.value: normalmente0(a menos que envie valor).data: dados da chamada (ABI-encoded).gasLimit: limite de gás desejado para a chamada.deadline: timestamp UNIX em que o permit expira.chainId: ID da rede.nonce: obtido vianonces(from)no precompile.
Execute node getSignature.js e copie v, r, s para usar na chamada dispatch.
Remember
Guarde chaves privadas em segurança; o exemplo destina-se apenas a testes.
Despachar a chamada¶
- No Remix, expanda o contrato Call Permit.
- Preencha os mesmos argumentos usados para assinar (
from,to,value,data,gasLimit,deadline,v,r,s). - Clique em transact e confirme no MetaMask.
- Verifique o efeito no contrato (
SetMessage.sol> get deve retornar a nova mensagem).
Parabéns! Você assinou e despachou uma chamada com o Call Permit Precompile.
| Criada: 9 de dezembro de 2025