Interagindo com o Call Permit Precompile¶
Introdução¶
O Call Permit Precompile em redes EVM powered by Tanssi permite que um usuário assine um permit, uma mensagem assinada EIP-712, para qualquer chamada EVM, podendo ser despachada por qualquer pessoa ou contrato inteligente. É semelhante ao Permit Signing das aprovações ERC-20 introduzidas no EIP-2612, exceto que se aplica a qualquer chamada EVM em vez de apenas aprovações.
Quando o call permit é despachado, isso é feito em nome do usuário que assinou o permit e o usuário ou contrato que despacha o permit é responsável por pagar as taxas de transação. Assim, o precompile pode ser usado para realizar transações sem gás para o signatário.
Por exemplo, Alice assina um call permit e Bob o despacha, executando a chamada em nome de Alice. Bob paga as taxas de transação; portanto, Alice não precisa ter moeda nativa para pagar a transação, a menos que a chamada inclua uma transferência.
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 do Call Permit¶
CallPermit.sol é uma interface Solidity que permite interagir com três métodos do precompile.
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);
}
A interface inclui as seguintes funções:
dispatch(address from, address to, uint256 value, bytes data, uint64[] gaslimit, uint256 deadline, uint8 v, bytes32 r, bytes32 s) — despacha uma chamada em nome de outro usuário com um permit EIP-712. Qualquer pessoa ou contrato pode chamar. A transação reverte se o permit for inválido ou se a chamada despachada reverter/errar (por exemplo, out of gas). Se for bem-sucedida, o nonce do signatário é incrementado para evitar replay
from- signatário do permit. A chamada será despachada em nome deste endereçoto- endereço para o qual a chamada é feitavalue- valor transferido da contafromdata- call data, ou ação a executarvalue- valor transferido da contafromgasLimit- limite de gás exigido pela chamada despachada. Informar este parâmetro evita que o despachante manipule o gas limitdeadline- tempo em segundos UNIX após o qual o permit não será mais válido. Em JavaScript, você pode obter o tempo UNIX atual executandoconsole.log(Date.now())em um script ou no console do navegadorv- recovery ID da assinatura (1 byte final da assinatura concatenada)r- primeiros 32 bytes da assinatura concatenadas- segundos 32 bytes da assinatura concatenada
nonces(address owner) — retorna o nonce atual para o owner informado
owner- endereço da conta a verificar
DOMAIN_SEPARATOR() — retorna o separador de domínio EIP-712 usado para evitar ataques de replay. Segue a implementação do EIP-2612
Nenhum
O separador de domínio EIP-712 usado para evitar ataques de replay.
O separador de domínio é definido no padrão EIP-712 e calculado como:
keccak256(PERMIT_DOMAIN, name, version, chain_id, address)
Os parâmetros do hash podem ser decompostos assim:
- PERMIT_DOMAIN - é o
keccak256deEIP712Domain(string name,string version,uint256 chainId,address verifyingContract) - name - é o nome do domínio de assinatura e deve ser exatamente
'Call Permit Precompile' - version - é a versão do domínio de assinatura. Aqui, version é
1 - chainId - é o Chain ID da sua rede
- verifyingContract - é o endereço do contrato que verificará a assinatura. Neste caso, o endereço do Call Permit Precompile
Quando dispatch é chamado, o permit precisa ser verificado antes de despachar a chamada. O primeiro passo é calcular o separador de domínio. O cálculo pode ser visto na implementação do Moonbeam ou em um exemplo prático no contrato EIP712 do OpenZeppelin.
A partir daí, um hash da assinatura e dos argumentos é gerado, garantindo que a assinatura só possa ser usada para o call permit. Ele usa um nonce para evitar replay. É semelhante ao contrato ERC20Permit do OpenZeppelin, exceto que o PERMIT_TYPEHASH é para call permit e os argumentos correspondem aos da função dispatch mais o nonce.
O separador de domínio e o hash struct podem ser usados para construir o hash final da mensagem totalmente codificada. Um exemplo prático está no contrato EIP712 do OpenZeppelin.
Com o hash final e os valores v, r e s, a assinatura pode ser verificada e recuperada. Se verificada com sucesso, o nonce é incrementado em um e a chamada é despachada.
Preparar os contratos¶
Neste exemplo, você aprenderá a assinar um call permit que atualiza uma mensagem em um contrato simples, SetMessage.sol. Antes de gerar a assinatura do call permit, é preciso implantar o contrato e definir os argumentos da função dispatch para o permit.
Depois de configurar o contrato de exemplo, você poderá configurar o contrato do Call Permit Precompile.
Verificando Pré-requisitos¶
Para acompanhar este tutorial, configure sua carteira para sua rede EVM e tenha uma conta com tokens nativos. Você pode adicionar sua rede EVM à MetaMask com um clique no Tanssi dApp. Ou configurar a MetaMask para a Tanssi com a rede EVM de demonstração.
Contrato de Exemplo¶
O contrato SetMessage.sol é perfeito para demonstrar o uso do Call Permit Precompile.
// 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;
}
}
Configuração do Remix¶
Você pode usar o Remix para compilar e implantar o contrato de exemplo. Tenha uma cópia de SetMessage.sol e de CallPermit.sol. Para adicioná-los ao Remix:
- Clique na aba File explorer
- Cole o contrato
CallPermit.solem um arquivo do Remix chamadoCallPermit.sol - Cole o contrato
SetMessage.solem um arquivo do Remix chamadoSetMessage.sol
Compile e implante o contrato de exemplo¶
Primeiro, compile o contrato de exemplo:
- Clique na aba Compile
- Para compilar a interface, clique em Compile SetMessage.sol
Em seguida, implante-o:
- Clique na aba Deploy and Run, logo abaixo da aba Compile no Remix. Observação: aqui você está implantando um contrato
- Certifique-se de que Injected Provider - Metamask está selecionado no menu ENVIRONMENT
- Garanta que SetMessage.sol esteja selecionado no menu CONTRACT
- Clique em Deploy
- A MetaMask aparecerá e você deverá Confirmar a transação
O contrato aparecerá na lista de Deployed Contracts no painel à esquerda. Copie o endereço do contrato, pois você precisará dele para gerar a assinatura do call permit na próxima seção.
Compile e acesse o Call Permit Precompile¶
Primeiro, compile o contrato Call Permit Precompile:
- Clique na aba Compile
- Para compilar a interface, clique em Compile CallPermit.sol
Depois, em vez de implantar o contrato, basta acessá-lo informando o endereço do precompile:
- Clique na aba Deploy and Run, logo abaixo da aba Compile no Remix. Observação: aqui você não implanta um contrato; apenas acessa um contrato pré-compilado já implantado
- Certifique-se de que Injected Provider - Metamask está selecionado no menu ENVIRONMENT
- Garanta que CallPermit.sol esteja selecionado no menu CONTRACT. Como é um contrato pré-compilado, não há etapa de deployment. Forneça o endereço do precompile no campo At Address
- Forneça o endereço do Call Permit Precompile para redes EVM powered by Tanssi:
0x0000000000000000000000000000000000000802e clique em At Address - O Call Permit Precompile aparecerá na lista de Deployed Contracts
Gerar a Assinatura do Call Permit¶
Para interagir com o Call Permit Precompile, você precisa ter ou gerar uma assinatura para despachar o call permit. Há várias formas de gerar a assinatura. Este guia mostra como fazê-lo usando o Ethers.js.
Veja um resumo dos passos para obter a assinatura:
- Criar a
message, incluindo parte dos dados necessários para o call permit: os argumentos da funçãodispatche o nonce do signatário - Montar a estrutura JSON dos dados a serem assinados, incluindo todos os tipos dos argumentos de
dispatche o nonce. Isso gera o tipoCallPermit, salvo comoprimaryType - Criar o domain separator usando exatamente
"Call Permit Precompile"para o nome, a versão do seu dApp ou plataforma, o Chain ID da rede em que a assinatura será usada e o endereço do contrato que verificará a assinatura. Você deve especificar o Chain ID da sua rede no script para gerar a assinatura correta - Assinar todos os dados montados usando Ethers.js
- A assinatura será retornada; use o
Signature.fromdo Ethers.js para obter os valoresv,res
Argumentos do Call Permit¶
Como visto na seção Interface do Call Permit, a função dispatch recebe os parâmetros: from, to, value, data, gasLimit, deadline, v, r e s.
Para obter os argumentos da assinatura (v, r e s), você deve assinar uma mensagem contendo os argumentos para os demais parâmetros acima, além do nonce do signatário.
from- endereço da conta com a qual você assinará o call permitto- endereço do contratoSetMessage.solvalue- pode ser0neste exemplo, já que apenas definiremos uma mensagem (sem transferir fundos)data- você pode enviar qualquer mensagem; precisa da representação hex da mensagem a definir no contratoSetMessage.sol. Ela inclui o function selector da funçãosete a string da mensagem. Para este exemplo, usehello worldcom a seguinte representação hex:0x4ed3885e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000gasLimit-100000é suficiente para enviar a chamada despachadadeadline- obtenha o horário UNIX atual executandoconsole.log(Date.now())em um script JavaScript ou no console do navegador. Depois, adicione segundos extras para definir quando o call permit expira
O nonce do signatário também é necessário. Se for a primeira vez que assina um call permit, o nonce será 0. Você também pode verificar o nonce no Remix:
- Expanda o contrato do call permit
- Ao lado da função nonces, insira o endereço do signatário e clique em nonces
- O resultado aparecerá logo abaixo da função
Use Ethers para Criar a Assinatura¶
Para gerar a assinatura do call permit usando JavaScript e Ethers, primeiro crie um projeto local:
mkdir call-permit-example && cd call-permit-example && touch getSignature.js
npm init -y
Agora você tem um arquivo para o script e um package.json. Abra o package.json e abaixo de "dependencies" adicione:
"type": "module"
Em seguida, instale o Ethers.js:
npm i ethers
Remember
Nunca revele suas chaves privadas, pois elas dão acesso direto aos fundos. Os passos a seguir são apenas demonstrativos.
No arquivo getSignature.js, copie e edite o trecho a seguir. Além dos campos discutidos na seção Argumentos do Call Permit, você deve inserir o Chain ID da sua rede no Domain Separator para gerar a assinatura corretamente. Se usar um Chain ID incorreto, a assinatura será inválida e nenhuma transação poderá ser despachada.
getSignature.js
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);
Para executar o script:
node getSignature.js
No console, você verá a assinatura concatenada e os valores v, r e s. Copie-os, pois serão usados ao interagir com o Call Permit Precompile nas próximas seções.
Note
Tome cuidado ao copiar os valores v, r e s para o método dispatch do precompile. A ordem no precompile pode não coincidir com a ordem de saída do script.
Interaja com a Interface Solidity¶
Agora que você gerou a assinatura do call permit, poderá testar a chamada da função dispatch do Call Permit Precompile.
Despachar uma Chamada¶
Ao enviar a função dispatch, use os mesmos argumentos que serviram para assinar o call permit. Para começar, volte à aba Deploy and Run no Remix e, em Deployed Contracts, expanda o contrato do call permit. Certifique-se de estar conectado à conta que consumirá o call permit e pagará as taxas. Em seguida:
- No campo from, informe o endereço da conta usada para assinar o call permit
- Copie e cole o endereço do contrato
SetMessage.sol - Informe
0no campo value - Insira a representação hex do function selector da função
sete a string que deseja definir como mensagem no contratoSetMessage.sol. Para este exemplo, usehello world:0x4ed3885e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000 - Informe
100000no campo gasLimit - Informe o
deadlineusado ao assinar o call permit - Copie o valor
vobtido ao gerar a assinatura do call permit e cole em v - Copie o valor
robtido ao gerar a assinatura do call permit e cole em r - Copie o valor
sobtido ao gerar a assinatura do call permit e cole em s - Clique em transact para enviar a transação
- A MetaMask aparecerá para confirmar; clique em Confirm
Quando a transação for concluída, você poderá verificar se a mensagem foi atualizada para hello world. Para isso:
- Expanda o contrato
SetMessage.sol - Clique em get
- O resultado aparecerá abaixo da função e deve exibir
hello world
Parabéns! Você gerou uma assinatura de call permit e a usou para despachar uma chamada em nome do signatário.
| Criada: 7 de janeiro de 2026








