Ir para o conteúdo

Interagindo com o Batch Precompile

Introdução

O contrato Batch Precompile em redes EVM powered by Tanssi permite agrupar várias chamadas EVM em uma só.

Normalmente, fazer o usuário interagir com vários contratos exige várias confirmações de transação na carteira. Um exemplo seria aprovar o acesso de um contrato a um token e logo em seguida transferi-lo. Com o Batch Precompile, você melhora a experiência do usuário com transações em lote, pois reduz o número de confirmações necessárias. Além disso, as taxas de gás podem diminuir, já que o batching evita múltiplas taxas base (as 21000 unidades iniciais de gás de cada transação).

O precompile interage diretamente com o pallet EVM do Substrate. Quem chama a função em lote tem seu endereço agindo como msg.sender para todas as subtransações, mas, diferente de delegate calls, o contrato de destino ainda altera o próprio armazenamento. É como se o usuário assinasse várias transações, mas com apenas uma confirmação.

O Batch Precompile está localizado neste endereço:

0x0000000000000000000000000000000000000801

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.

A Interface Solidity em Lote

Batch.sol é uma interface Solidity que permite interagir com os três métodos do precompile.

Batch.sol
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @dev The Batch contract's address.
address constant BATCH_ADDRESS = 0x0000000000000000000000000000000000000801;

/// @dev The Batch contract's instance.
Batch constant BATCH_CONTRACT = Batch(BATCH_ADDRESS);

/// @author The Moonbeam Team
/// @title Batch precompile
/// @dev Allows to perform multiple calls throught one call to the precompile.
/// Can be used by EOA to do multiple calls in a single transaction.
/// @custom:address 0x0000000000000000000000000000000000000801
interface Batch {
    /// @dev Batch multiple calls into a single transaction.
    /// All calls are performed from the address calling this precompile.
    ///
    /// In case of one subcall reverting following subcalls will still be attempted.
    ///
    /// @param to List of addresses to call.
    /// @param value List of values for each subcall. If array is shorter than "to" then additional
    /// calls will be performed with a value of 0.
    /// @param callData Call data for each `to` address. If array is shorter than "to" then
    /// additional calls will be performed with an empty call data.
    /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas.
    /// If array is shorter than "to" then the remaining gas available will be used.
    /// @custom:selector 79df4b9c
    function batchSome(
        address[] memory to,
        uint256[] memory value,
        bytes[] memory callData,
        uint64[] memory gasLimit
    ) external;

    /// @dev Batch multiple calls into a single transaction.
    /// All calls are performed from the address calling this precompile.
    ///
    /// In case of one subcall reverting, no more subcalls will be executed but
    /// the batch transaction will succeed. Use batchAll to revert on any subcall revert.
    ///
    /// @param to List of addresses to call.
    /// @param value List of values for each subcall. If array is shorter than "to" then additional
    /// calls will be performed with a value of 0.
    /// @param callData Call data for each `to` address. If array is shorter than "to" then
    /// additional calls will be performed with an empty call data.
    /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas.
    /// If array is shorter than "to" then the remaining gas available will be used.
    /// @custom:selector cf0491c7
    function batchSomeUntilFailure(
        address[] memory to,
        uint256[] memory value,
        bytes[] memory callData,
        uint64[] memory gasLimit
    ) external;

    /// @dev Batch multiple calls into a single transaction.
    /// All calls are performed from the address calling this precompile.
    ///
    /// In case of one subcall reverting, the entire batch will revert.
    ///
    /// @param to List of addresses to call.
    /// @param value List of values for each subcall. If array is shorter than "to" then additional
    /// calls will be performed with a value of 0.
    /// @param callData Call data for each `to` address. If array is shorter than "to" then
    /// additional calls will be performed with an empty call data.
    /// @param gasLimit Gas limit for each `to` address. Use 0 to forward all the remaining gas.
    /// If array is shorter than "to" then the remaining gas available will be used.
    /// @custom:selector 96e292b8
    function batchAll(
        address[] memory to,
        uint256[] memory value,
        bytes[] memory callData,
        uint64[] memory gasLimit
    ) external;

    /// Emitted when a subcall succeeds.
    event SubcallSucceeded(uint256 index);

    /// Emitted when a subcall fails.
    event SubcallFailed(uint256 index);
}

A interface inclui as seguintes funções:

batchSome(address[] to, uint256[] value, bytes[] callData, uint64[] gasLimit) — executa várias chamadas, combinando os mesmos índices dos arrays para formar cada subchamada. Se uma subchamada reverter, as seguintes ainda serão tentadas
  • to - array de endereços para direcionar as subtransações, em que cada entrada é uma subtransação
  • value - array de valores em moeda nativa para enviar nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Se este array for menor que o array to, todas as subtransações seguintes terão valor 0
  • callData - array de dados de chamada para incluir nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Se este array for menor que o array to, todas as subtransações seguintes não terão dados de chamada
  • gasLimit - array de limites de gás nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Valores 0 são interpretados como ilimitados e encaminham todo o gás restante da transação em lote. Se este array for menor que o array to, todas as subtransações seguintes encaminharão todo o gás restante
batchSomeUntilFailure(address[] to, uint256[] value, bytes[] callData, uint64[] gasLimit) — executa várias chamadas, combinando os mesmos índices dos arrays para formar cada subchamada. Se uma subchamada reverter, nenhuma subchamada seguinte será executada
  • to - array de endereços para direcionar as subtransações, em que cada entrada é uma subtransação
  • value - array de valores em moeda nativa para enviar nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Se este array for menor que o array to, todas as subtransações seguintes terão valor 0
  • callData - array de dados de chamada para incluir nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Se este array for menor que o array to, todas as subtransações seguintes não terão dados de chamada
  • gasLimit - array de limites de gás nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Valores 0 são interpretados como ilimitados e encaminham todo o gás restante da transação em lote. Se este array for menor que o array to, todas as subtransações seguintes encaminharão todo o gás restante
batchAll(address[] to, uint256[] value, bytes[] callData, uint64[] gasLimit) — executa várias chamadas de forma atômica, combinando os mesmos índices dos arrays para formar cada subchamada. Se uma subchamada reverter, todas as subchamadas irão reverter
  • to - array de endereços para direcionar as subtransações, em que cada entrada é uma subtransação
  • value - array de valores em moeda nativa para enviar nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Se este array for menor que o array to, todas as subtransações seguintes terão valor 0
  • callData - array de dados de chamada para incluir nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Se este array for menor que o array to, todas as subtransações seguintes não terão dados de chamada
  • gasLimit - array de limites de gás nas subtransações, em que o índice corresponde à subtransação no mesmo índice em to. Valores 0 são interpretados como ilimitados e encaminham todo o gás restante da transação em lote. Se este array for menor que o array to, todas as subtransações seguintes encaminharão todo o gás restante

A interface também inclui os seguintes eventos:

  • SubcallSucceeded(uint256 index) - emitido quando uma subchamada do índice informado é bem-sucedida
  • SubcallFailed(uint256 index) - emitido quando uma subchamada do índice informado falha

Interaja com a Interface Solidity

Verificando Pré-requisitos

Para acompanhar este tutorial, você precisa ter a carteira configurada para sua rede EVM e 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 SimpleContract.sol será usado como exemplo de interação em lote, mas, na prática, qualquer contrato pode ser usado.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;

contract SimpleContract {
    mapping(uint256 => string) public messages;

    function setMessage(uint256 id, string calldata message) external {
        messages[id] = message;
    }
}

Configuração do Remix

Você pode interagir com o Batch Precompile usando o Remix. Tenha uma cópia de Batch.sol e de SimpleContract.sol. Para adicionar o precompile no Remix e seguir o tutorial:

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

Compile o Contrato

Em seguida, compile os dois arquivos no Remix:

  1. Certifique-se de que o arquivo Batch.sol está aberto
  2. Clique na aba Compile, a segunda de cima
  3. Para compilar, clique em Compile Batch.sol

Compiling Batch.sol

Se a interface foi compilada com sucesso, você verá um check verde ao lado da aba Compile.

Acesse o Precompile

Em vez de implantar o Batch Precompile, acesse a interface informando o endereço do contrato pré-compilado:

  1. Clique na aba Deploy and Run logo abaixo da aba Compile no Remix. Observe que o contrato pré-compilado já está implantado
  2. Certifique-se de que Injected Provider - MetaMask está selecionado no menu ENVIRONMENT. Ao selecionar, a MetaMask pode solicitar que você conecte sua conta ao Remix
  3. Confirme que a conta correta aparece em ACCOUNT
  4. Garanta que Batch.sol está selecionado no menu CONTRACT. Como é um contrato pré-compilado, não é necessário implantar código. Vamos apenas fornecer o endereço do precompile no campo At Address
  5. Informe o endereço do Batch Precompile: 0x0000000000000000000000000000000000000801 e clique em At Address

Access the address

O precompile BATCH aparecerá na lista de Deployed Contracts.

Implemente o Contrato de Exemplo

Por outro lado, SimpleContract.sol será implantado como um novo contrato. Antes de começar esta seção, repita a etapa de compilação com o arquivo SimpleContract.sol.

  1. Clique na aba Deploy and Run logo abaixo da aba Compile no Remix
  2. Certifique-se de que Injected Provider - MetaMask está selecionado em ENVIRONMENT. Ao selecionar, a MetaMask pode solicitar que você conecte sua conta ao Remix
  3. Confirme que a conta correta aparece em ACCOUNT
  4. Garanta que SimpleContract está selecionado no menu CONTRACT
  5. Clique em Deploy
  6. Confirme a transação que aparecerá na MetaMask clicando em Confirm

Deploy SimpleContract

O contrato SIMPLECONTRACT aparecerá na lista de Deployed Contracts.

Envie moeda nativa via precompile

Enviar moeda nativa com o Batch Precompile exige mais do que alguns cliques no Remix ou na MetaMask. Neste exemplo, você usará a função batchAll para enviar moeda nativa de forma atômica.

Transações têm um campo value para indicar o valor de moeda nativa a enviar. No Remix, isso é definido pelo input VALUE na aba DEPLOY & RUN TRANSACTIONS. Porém, para o Batch Precompile, esses valores são fornecidos no array value das funções em lote.

Tente transferir o token nativo da sua rede para duas carteiras usando o Batch Precompile:

  1. Expanda o contrato do batch em Deployed Contracts
  2. Expanda a função batchAll
  3. No campo to, insira os endereços neste formato: ["INSERIR_ENDERECO_1", "INSERIR_ENDERECO_2"], onde o primeiro endereço corresponde à primeira carteira e o segundo à segunda carteira
  4. No campo value, insira o valor que deseja transferir em Wei para cada endereço. Por exemplo, ["1000000000000000000", "2000000000000000000"] transferirá 1 token nativo para o primeiro endereço e 2 tokens para o segundo
  5. Para callData, insira []. Não há dados de chamada para uma simples transferência de token nativo
  6. Para gasLimit, insira []
  7. Clique em transact
  8. Clique em Confirm na MetaMask para confirmar a transação

Send Batch Transfer

Quando a transação for concluída, você pode conferir os saldos das duas contas na MetaMask ou no explorador da sua rede (link no Tanssi dApp). Parabéns! Você enviou uma transferência em lote via Batch Precompile.

Note

Normalmente, para enviar moeda nativa para ou através de um contrato, seria preciso definir o value no objeto geral da transação e interagir com uma função payable. Contudo, como o Batch Precompile interage diretamente com o código Substrate, esta não é uma transação Ethereum típica, então isso não é necessário.

Descubra o call data de uma interação de contrato

Interfaces visuais como o Remix e bibliotecas como Ethers.js ocultam como transações Ethereum interagem com contratos Solidity. O nome e os tipos de entrada de uma função são transformados em um seletor de função e os dados de entrada são codificados. Esses dois elementos são combinados e enviados como o call data da transação. Para enviar uma subtransação dentro de uma transação em lote, o remetente precisa conhecer previamente esse call data.

Tente encontrar o call data de uma transação usando o Remix:

  1. Expanda o contrato SimpleContract.sol em Deployed Contracts
  2. Expanda a função setMessage
  3. Insira o id desejado, como 1
  4. Insira a message desejada, como "tanssi"
  5. Em vez de enviar a transação, clique no botão de copiar ao lado de transact para copiar o call data

Transaction Call Data

Agora você tem o call data da transação! Considerando os valores de exemplo 1 e "tanssi", podemos observar seus valores codificados no call data:

0x648345c8                                                        // function selector
0000000000000000000000000000000000000000000000000000000000000001  // 1 id
0000000000000000000000000000000000000000000000000000000000000040  // 32 byte offset
000000000000000000000000000000000000000000000000000000000000000   // 32 byte length
674616e7373690000000000000000000000000000000000000000000000000000 // "tanssi" em bytes

O call data pode ser dividido em cinco linhas em que:

  • A primeira linha é o seletor de função
  • A segunda linha é igual a 1, que é o id fornecido
  • O restante envolve o input message. Essas três últimas linhas são mais complexas, pois strings são um tipo dinâmico com tamanho variável. A terceira linha se refere ao offset que define onde os dados da string começam. A quarta linha se refere ao comprimento da mensagem na linha seguinte, que é de 32 bytes no total — a mensagem "tanssi" mais o preenchimento

Você pode repetir as etapas acima para capturar o call data para os valores 2 e "hello" e enviar várias subchamadas de forma atômica com o Batch Precompile na próxima seção.

Interação de Função via Precompile

O exemplo desta seção usará a função batchAll, que garante a resolução atômica das transações. Lembre-se de que há outras duas funções em lote que podem continuar subtransações apesar de erros ou parar subtransações seguintes sem reverter as anteriores.

Interagir com uma função é muito semelhante a enviar moeda nativa, já que ambas são transações. Entretanto, é necessário call data para fornecer entradas às funções corretamente, e o remetente pode querer limitar o gás gasto em cada subtransação.

Os campos callData e gasLimit são mais relevantes para subtransações que interagem com contratos. Para cada função da interface em lote, callData é um array em que cada índice corresponde ao call data de cada destinatário da subtransação, ou seja, cada entrada em to. Se o tamanho do array callData for menor que o array to, as subtransações restantes não terão call data (funções sem entradas). O gasLimit é um array que define quanto gás cada subtransação pode gastar. Se o valor em um índice for 0 ou o índice estiver fora do tamanho do array (mas ainda menor que o tamanho de to), todo o gás restante da transação anterior é encaminhado.

Para usar o precompile e enviar uma transação em lote atômica combinando duas interações de contrato, faça o seguinte:

  1. Copie o endereço do contrato SimpleContract.sol com o botão de copiar à direita do cabeçalho. Tenha também o call data da seção anterior
  2. Expanda o contrato do batch em Deployed Contracts
  3. Expanda a função batchAll
  4. Para o campo to, cole o endereço de SimpleContract.sol assim: ["INSERIR_ENDERECO_SIMPLE_CONTRACT","INSERIR_ENDERECO_SIMPLE_CONTRACT"]. Observe que é preciso repetir o endereço para cada transação em lote, mesmo que o contrato seja o mesmo
  5. Para o campo value, como SimpleContract.sol não requer moeda nativa, insira [0,0] para 0 Wei
  6. Para o campo callData, insira os call data da seção anterior neste formato: ["INSERIR_PRIMEIRO_CALL_DATA","INSERIR_SEGUNDO_CALL_DATA"]
  7. Para o campo gasLimit, insira []. Você pode definir um valor de gás para cada subchamada ou deixar como array vazio
  8. Clique em transact
  9. Clique em Confirm na MetaMask para confirmar a transação

Batch Function Interaction

Se você usou o mesmo call data do tutorial, pode verificar se a transação deu certo assim:

  1. Expanda o contrato SimpleContract.sol em Deployed Contracts
  2. À direita do botão messages, insira 1
  3. Clique no botão azul messages

SimpleContract Confirmation

A frase "tanssi" deve aparecer embaixo. Você pode repetir com o id "2" e verá "hello". Parabéns! Você interagiu com uma função usando o Batch Precompile.

Combinando Subtransações

Até aqui, transferir moeda nativa e interagir com funções foram ações separadas, mas elas podem ser combinadas.

As quatro strings a seguir podem ser usadas como inputs de uma transação em lote. Elas enviam 1 token nativo para a conta pública Gerald (0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b) e interagem com um contrato SimpleContract.sol pré-implantado duas vezes. Eis o detalhamento:

Há três subtransações que correspondem a três endereços no array to. O primeiro é a conta pública Gerald e os dois seguintes são um contrato SimpleContract.sol. Você pode substituir os dois últimos pelo seu próprio contrato SimpleContract.sol se quiser. Ou substituir apenas um: é possível interagir com múltiplos contratos em uma única mensagem.

[
  "0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b",
  "0xd14b70a55F6cBAc06d4FA49b99be0370D0e1BD39", 
  "0xd14b70a55F6cBAc06d4FA49b99be0370D0e1BD39"
]

Também haverá três valores para o array value. O primeiro endereço em to indica 1000000000000000000 wei ou 1 UNIT do token nativo. Lembre que os tokens nativos de redes EVM powered by Tanssi têm 18 casas decimais, assim como no Ethereum. Os dois valores seguintes são 0 porque a função com que suas subtransações interagem não aceita nem exige moeda nativa.

["1000000000000000000", "0", "0"]

Você precisará de três valores para o array callData. Como transferir moeda nativa não requer call data, a string fica vazia. O segundo e o terceiro valores correspondem a chamadas de setMessage que definem mensagens para os IDs 5 e 6.

[
  "0x", 
  "0x648345c8000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000009796f752061726520610000000000000000000000000000000000000000000000", 
  "0x648345c800000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e61206d6f6f6e6265616d2070726f000000000000000000000000000000000000"
]

O input final é para gas_input. Este array ficará vazio para encaminhar todo o gás restante a cada subtransação.

[]

Tente enviar uma transação em lote com esses inputs no Remix da mesma forma que você agrupou uma chamada de função.

E é isso! Você interagiu com o precompile de batching usando MetaMask e Remix!

Bibliotecas de Desenvolvimento Ethereum

Se você seguiu o tutorial de Ethers.js, pode ser difícil encontrar o call data de uma função. A resposta está escondida no objeto Interface do Ethers, onde a função encodeFunctionData permite informar o nome da função e os inputs para obter o call data resultante. O Web3.js tem função semelhante, encodeFunctionCall.

Note

Os trechos de código a seguir não são destinados a ambientes de produção. Adapte-os conforme cada caso de uso.

// Import the contract ABI
const { abi } = require('./INSERT_ABI_PATH');

// Use ABI to create an interface
const yourContractInterface = new ethers.Interface(abi);

// Find call data for the setMessage function
const callData = yourContractInterface.encodeFunctionData(
  'INSERT_FUNCTION_NAME',
  [
    'INSERT_INPUT_1',
    'INSERT_INPUT_2',
    // ...
  ]
);
// Import the contract ABI
const { abi } = require('./INSERT_ABI_PATH');

// Find call data for the setMessage function
const callData = web3.eth.abi.encodeFunctionCall(abi, [
  'INSERT_INPUT_1',
  'INSERT_INPUT_2',
  // ...
]);
# Import the ABI and bytecode
from compile import abi, bytecode

# Create contract instance
your_contract = web3.eth.contract(abi=abi, bytecode=bytecode)

# Encode the contract call
call_data = your_contract.encodeABI(
    fn_name="INSERT_FUNCTION_NAME", args=["INSERT_INPUT_1", "INSERT_INPUT_2", ...]
)

Depois disso, você estará pronto para interagir com o Batch Precompile como faria normalmente com um contrato no Ethers.

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