Skip to main content

Execute from Ethereum

Execute transactions from Ethereum to Signet with guaranteed inclusion

The Transactor contract provides guaranteed, censorship-resistant execution on Signet from Ethereum. Any Ethereum account can call Transactor to trigger a transaction on Signet that builders cannot censor or reorder.

Overview

Transactor.sol exposes two function signatures for sending transactions to Signet:

Default Chain

solidity
function transact(
    address to,
    bytes calldata data,
    uint256 value,
    uint256 gas,
    uint256 maxFeePerGas
) external payable

This version uses the contract’s defaultRollupChainId configured at deployment.

Explicit Chain

solidity
function transact(
    uint256 rollupChainId,
    address to,
    bytes calldata data,
    uint256 value,
    uint256 gas,
    uint256 maxFeePerGas
) external payable

Use this version when targeting a specific Signet chain in multi-chain deployments.


Calling either transact function results in a system transaction being issued on Signet from the address of the caller. This allows Ethereum contracts to own assets on Signet, and to trigger contract execution on Signet from Ethereum with native authentication.

The value argument is the amount of USD in wei to send on the rollup. USD is the native asset of Signet. This amount will be attached to the Signet transaction. The balance of the originating address on Signet must be sufficient to cover this value.

Transact events are executed at the end of the Signet block. Contract creation via Transactor is not currently supported.

The Transactor emits a Transact event that can be monitored. The Signet node listens for these events directly, so if the event fired, the transaction was included in Signet, although the inner call may revert.

This page assumes you’ve set up your clients.

Sometimes an Ethereum contract needs to trigger execution on Signet: a DAO proposal passes, a governance timelock expires, a multisig approves a parameter change. The Transactor contract handles this. It guarantees transaction inclusion on Signet in the same block, bypassing builder discretion.

How Transactor works

The Transactor contract lives on Ethereum. You call transact with the Signet call you want to execute. The system creates a transaction on Signet from the caller’s address, executing at the end of the Signet block. No builder can exclude it.

Normally, builders choose which transactions to include in Signet blocks. If a builder has a reason to censor yours (MEV extraction, competitive pressure), you have no recourse through the normal path. Transactor bypasses builder discretion entirely.

When you need it

Direct Signet transactions are cheaper and simpler. Transactor exists for three situations:

Your logic lives on Ethereum. The Ethereum contract calls Transactor directly, with no off-chain relayer and no trust assumptions.

You need censorship resistance. The transaction executes unconditionally. This matters for liquidations, time-sensitive governance, or any operation where inclusion is non-negotiable.

You want L1 security for the trigger. The Ethereum transaction is the source of authority. Signet execution follows deterministically.

Contracts

ContractAddress
Transactor (host)0x0B4fc18e78c585687E01c172a1087Ea687943db9

All contract addresses on this page are for Parmigiana testnet.

Execute a transaction

Encode the Signet call and submit through Transactor on Ethereum:

typescript
import { transactorAbi } from '@signet-sh/sdk/abi';
import { PARMIGIANA } from '@signet-sh/sdk/constants';
import { encodeFunctionData, parseGwei } from 'viem';

const innerData = encodeFunctionData({
  abi: targetContractAbi,
  functionName: 'someFunction',
  args: [arg1, arg2],
});

const hash = await hostWalletClient.writeContract({
  address: PARMIGIANA.hostTransactor,
  abi: transactorAbi,
  functionName: 'transact',
  args: [
    signetTargetAddress,
    innerData,
    0n,              // USD value on Signet
    1_000_000n,      // gas limit
    parseGwei('100'), // max fee per gas
  ],
});
Without the SDK
typescript
import { encodeFunctionData, parseGwei } from 'viem';

const transactorAbi = [
  {
    name: 'transact',
    type: 'function',
    stateMutability: 'payable',
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'data', type: 'bytes' },
      { name: 'value', type: 'uint256' },
      { name: 'gas', type: 'uint256' },
      { name: 'maxFeePerGas', type: 'uint256' },
    ],
    outputs: [],
  },
] as const;

const innerData = encodeFunctionData({
  abi: targetContractAbi,
  functionName: 'someFunction',
  args: [arg1, arg2],
});

const hash = await hostWalletClient.writeContract({
  address: '0x0B4fc18e78c585687E01c172a1087Ea687943db9',
  abi: transactorAbi,
  functionName: 'transact',
  args: [
    signetTargetAddress,
    innerData,
    0n,
    1_000_000n,
    parseGwei('100'),
  ],
});

Verifying execution

The Transact event on Ethereum confirms your transaction was included on Signet, but the inner call can still revert. Check the Signet transaction receipt for the corresponding block to confirm success.

typescript
const receipt = await hostPublicClient.waitForTransactionReceipt({ hash });
// Transact event confirms inclusion on Signet
// Check the Signet transaction receipt for the actual result

When to use something else

Next steps

  • Passage moves assets to Signet without guaranteed inclusion
  • EVM behavior covers address aliasing details for contract callers

This page assumes you’ve set up your providers.

The Transactor contract lives on Ethereum. You call transact with the Signet call you want to execute, and the system creates a transaction on Signet from the caller’s address. No builder can exclude it.

See Parmigiana Testnet for the Transactor contract address.

Execute a transaction

Encode the calldata for your target Signet contract, then call transact on the Transactor contract via the host chain provider:

rust
use alloy::primitives::{utils::parse_units, Address, Bytes, U256};
use alloy::providers::{Provider, ProviderBuilder};
use signet_constants::parmigiana;
use signet_zenith::Transactor;

// Encode the function call for the target Signet contract
let calldata: Bytes = /* your encoded calldata */;

let transactor = Transactor::new(parmigiana::HOST_TRANSACTOR, &host_provider);

let tx = transactor
    .transact_0(
        signet_target_address,   // target contract on Signet
        calldata,                // encoded function call
        U256::ZERO,              // USD value on Signet
        U256::from(1_000_000),   // gas limit
        parse_units("100", "gwei")?.into(), // max fee per gas
    )
    .send()
    .await?;

let receipt = tx.get_receipt().await?;

Use transact_1 instead of transact_0 to specify an explicit rollup chain ID for multi-chain deployments.

Verifying execution

The Transact event on Ethereum confirms your transaction was included on Signet, but the inner call can still revert. Check the Signet transaction receipt for the corresponding block to confirm success.

Next steps

This page assumes you’ve completed the getting started setup.

solidity
import {Transactor} from "zenith/src/Transactor.sol";

contract YourContract {
    Transactor public transactor;

    constructor(Transactor _transactor) {
        transactor = _transactor;
    }

    // Execute a function on Signet from Ethereum
    function executeOnSignet(
        address signetTarget,
        bytes calldata functionData
    ) external {
        transactor.transact(
            signetTarget,
            functionData,
            0, // USD value on Signet
            1_000_000, // gas limit on Signet
            100 gwei // max fee per gas (in USD) on Signet
        );
    }

    // Emergency action on Signet
    function emergencyAction(address signetContract) external onlyOwner {
        // Encode the emergency function call
        bytes memory data = abi.encodeWithSignature("emergencyPause()");
        executeOnSignet(signetContract, data);
    }
}

Source

Call transact (default chain)

The simpler form uses the contract’s defaultRollupChainId:

bash
cast send 0x0B4fc18e78c585687E01c172a1087Ea687943db9 \
  "transact(address,bytes,uint256,uint256,uint256)" \
  $SIGNET_TARGET \
  $CALLDATA \
  0 \
  1000000 \
  100000000000 \
  --rpc-url $HOST_RPC \
  --private-key $PRIVATE_KEY

The arguments:

  • to: target contract address on Signet
  • data: ABI-encoded calldata for the Signet function
  • value: USD (in wei) to attach on Signet. The caller’s Signet balance must cover this.
  • gas: gas limit for the Signet transaction
  • maxFeePerGas: max fee per gas in USD (here, 100 gwei)

Call transact (explicit chain ID)

For multi-chain deployments, specify the rollup chain ID directly:

bash
cast send 0x0B4fc18e78c585687E01c172a1087Ea687943db9 \
  "transact(uint256,address,bytes,uint256,uint256,uint256)" \
  88888 \
  $SIGNET_TARGET \
  $CALLDATA \
  0 \
  1000000 \
  100000000000 \
  --rpc-url $HOST_RPC \
  --private-key $PRIVATE_KEY

88888 is the Parmigiana rollup chain ID.

Encoding calldata

Use cast calldata to produce the $CALLDATA argument:

bash
CALLDATA=$(cast calldata "someFunction(uint256,address)" 42 0xYourAddress)

Then pass it to the transact call above.

Verifying execution

The Transact event on Ethereum confirms inclusion on Signet. Check for it in the transaction receipt:

bash
cast receipt $TX_HASH --rpc-url $HOST_RPC

The inner call on Signet can still revert. Check the corresponding Signet block for the actual result.

All contract addresses on this page are for Parmigiana testnet.

ESC

Start typing to search documentation...

Navigate Select ⌘K Open