Ir para o conteúdo

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ço
  • to - endereço para o qual a chamada é feita
  • value - valor transferido da conta from
  • data - call data, ou ação a executar
  • value - valor transferido da conta from
  • gasLimit - limite de gás exigido pela chamada despachada. Informar este parâmetro evita que o despachante manipule o gas limit
  • deadline - 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 executando console.log(Date.now()) em um script ou no console do navegador
  • v - recovery ID da assinatura (1 byte final da assinatura concatenada)
  • r - primeiros 32 bytes da assinatura concatenada
  • s - 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 keccak256 de EIP712Domain(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:

  1. Clique na aba File explorer
  2. Cole o contrato CallPermit.sol em um arquivo do Remix chamado CallPermit.sol
  3. Cole o contrato SetMessage.sol em um arquivo do Remix chamado SetMessage.sol

Copiando e colando o contrato de exemplo no Remix

Compile e implante o contrato de exemplo

Primeiro, compile o contrato de exemplo:

  1. Clique na aba Compile
  2. Para compilar a interface, clique em Compile SetMessage.sol

Compiling SetMessage.sol

Em seguida, implante-o:

  1. Clique na aba Deploy and Run, logo abaixo da aba Compile no Remix. Observação: aqui você está implantando um contrato
  2. Certifique-se de que Injected Provider - Metamask está selecionado no menu ENVIRONMENT
  3. Garanta que SetMessage.sol esteja selecionado no menu CONTRACT
  4. Clique em Deploy
  5. A MetaMask aparecerá e você deverá Confirmar a transação

Provide the address

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:

  1. Clique na aba Compile
  2. Para compilar a interface, clique em Compile CallPermit.sol

Compiling SetMessage.sol

Depois, em vez de implantar o contrato, basta acessá-lo informando o endereço do precompile:

  1. 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
  2. Certifique-se de que Injected Provider - Metamask está selecionado no menu ENVIRONMENT
  3. 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
  4. Forneça o endereço do Call Permit Precompile para redes EVM powered by Tanssi: 0x0000000000000000000000000000000000000802 e clique em At Address
  5. O Call Permit Precompile aparecerá na lista de Deployed Contracts

Provide the address

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:

  1. Criar a message, incluindo parte dos dados necessários para o call permit: os argumentos da função dispatch e o nonce do signatário
  2. Montar a estrutura JSON dos dados a serem assinados, incluindo todos os tipos dos argumentos de dispatch e o nonce. Isso gera o tipo CallPermit, salvo como primaryType
  3. 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
  4. Assinar todos os dados montados usando Ethers.js
  5. A assinatura será retornada; use o Signature.from do Ethers.js para obter os valores v, r e s

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 permit
  • to - endereço do contrato SetMessage.sol
  • value - pode ser 0 neste 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 contrato SetMessage.sol. Ela inclui o function selector da função set e a string da mensagem. Para este exemplo, use hello world com a seguinte representação hex:
    0x4ed3885e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000
    
  • gasLimit - 100000 é suficiente para enviar a chamada despachada
  • deadline - obtenha o horário UNIX atual executando console.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:

  1. Expanda o contrato do call permit
  2. Ao lado da função nonces, insira o endereço do signatário e clique em nonces
  3. O resultado aparecerá logo abaixo da função

Get the nonce

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.

Signature values in the console

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:

  1. No campo from, informe o endereço da conta usada para assinar o call permit
  2. Copie e cole o endereço do contrato SetMessage.sol
  3. Informe 0 no campo value
  4. Insira a representação hex do function selector da função set e a string que deseja definir como mensagem no contrato SetMessage.sol. Para este exemplo, use hello world:
    0x4ed3885e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b68656c6c6f20776f726c64000000000000000000000000000000000000000000
    
  5. Informe 100000 no campo gasLimit
  6. Informe o deadline usado ao assinar o call permit
  7. Copie o valor v obtido ao gerar a assinatura do call permit e cole em v
  8. Copie o valor r obtido ao gerar a assinatura do call permit e cole em r
  9. Copie o valor s obtido ao gerar a assinatura do call permit e cole em s
  10. Clique em transact para enviar a transação
  11. A MetaMask aparecerá para confirmar; clique em Confirm

Dispatch the call permit

Quando a transação for concluída, você poderá verificar se a mensagem foi atualizada para hello world. Para isso:

  1. Expanda o contrato SetMessage.sol
  2. Clique em get
  3. O resultado aparecerá abaixo da função e deve exibir hello world

Verify the dispatch was executed as intended

Parabéns! Você gerou uma assinatura de call permit e a usou para despachar uma chamada em nome do signatário.

As informações apresentadas aqui foram fornecidas por terceiros e estão disponíveis apenas para fins informativos gerais. A Tanssi não endossa nenhum projeto listado e descrito no Site de Documentação da Tanssi (https://docs.tanssi.network/). A Tanssi Foundation não garante a precisão, integridade ou utilidade dessas informações. Qualquer confiança depositada nelas é de sua exclusiva responsabilidade. A Tanssi Foundation se exime de toda responsabilidade decorrente de qualquer confiança que você ou qualquer outra pessoa possa ter em qualquer parte deste conteúdo. Todas as declarações e/ou opiniões expressas nesses materiais são de responsabilidade exclusiva da pessoa ou entidade que as fornece e não representam necessariamente a opinião da Tanssi Foundation. As informações aqui não devem ser interpretadas como aconselhamento profissional ou financeiro de qualquer tipo. Sempre busque orientação de um profissional devidamente qualificado em relação a qualquer assunto ou circunstância em particular. As informações aqui podem conter links ou integração com outros sites operados ou conteúdo fornecido por terceiros, e tais sites podem apontar para este site. A Tanssi Foundation não tem controle sobre esses sites ou seu conteúdo e não terá responsabilidade decorrente ou relacionada a eles. A existência de qualquer link não constitui endosso desses sites, de seu conteúdo ou de seus operadores. Esses links são fornecidos apenas para sua conveniência, e você isenta e exonera a Tanssi Foundation de qualquer responsabilidade decorrente do uso dessas informações ou das informações fornecidas por qualquer site ou serviço de terceiros.
Última atualização: 23 de dezembro de 2025
| Criada: 27 de novembro de 2025