Skip to main content

Updates

Passage and Transactor

Anthony //4 min read
technical

Bridges are complicated. Lockups, relayers, waiting periods, wrapped assets. Users learn to dread the “bridging” step of any cross-chain interaction.

Deposits shouldn’t be this hard.

Signet has two entry contracts: Passage for asset transfers, Transactor for arbitrary execution. Together they cover every L1→L2 pattern you’ll need—and they all settle in the same block.


Passage: Asset Entry

Passage handles moving assets from Ethereum into Signet. The contract emits events that Signet observes and acts on in the same block.

enter()

The simplest pattern. Send ETH, receive USD on Signet.

solidity
// On Ethereum (L1)
IPassage(PASSAGE).enter{value: 1 ether}(rollupRecipient);

What happens:

  1. Your ETH is held by Passage on L1
  2. Passage emits Enter(from, to, value)
  3. Signet observes the event and mints USD to rollupRecipient

No waiting. No claim step. The recipient can spend on Signet immediately.

You can also just send ETH directly:

solidity
// Fallback/receive — uses msg.sender as recipient
payable(PASSAGE).transfer(1 ether);

enterToken()

Same pattern, but for ERC-20s.

solidity
// Approve first
IERC20(token).approve(PASSAGE, amount);

// Then enter
IPassage(PASSAGE).enterToken(rollupRecipient, token, amount);

Emits EnterToken(from, to, token, amount). The token must be on Signet’s allowlist—currently WETH, WBTC, and USDC. Signet credits the corresponding native token.


Transactor: Arbitrary Execution

Transactor lets L1 contracts trigger execution on Signet without transferring assets. Your L1 transaction causes an L2 transaction to execute.

solidity
// On Ethereum (L1)
ITransactor(TRANSACTOR).transact{value: ethForGas}(
    to,           // Target contract on Signet
    data,         // Encoded function call
    value,        // USD to attach (in wei)
    gas,          // Gas limit for Signet execution
    maxFeePerGas  // Max fee per gas in USD
);

Signet observes the Transact event and executes the call at the end of the Signet block. The msg.sender on Signet is derived from the L1 caller.

Address Aliasing

When a smart contract calls Transactor, the Signet-side msg.sender is aliased:

text
signet_address = (l1_address + 0x1111000000000000000000000000000000001111) % 2^160

EOAs are not aliased—their address is unchanged. This prevents L1 contracts from impersonating L2 EOAs.

Cross-Chain Contract Control

An L1 contract can own and operate an L2 contract:

solidity
// L1 Controller
contract L1Controller {
    address constant TRANSACTOR = 0x0B4fc18e78c585687E01c172a1087Ea687943db9;
    address public l2Contract;

    function updateL2Config(uint256 newValue) external payable onlyOwner {
        ITransactor(TRANSACTOR).transact{value: msg.value}(
            l2Contract,
            abi.encodeCall(IL2Contract.setConfig, (newValue)),
            0,        // no USD value
            100000,   // gas limit
            1 gwei    // max fee per gas
        );
    }
}

The L2 contract sees msg.sender as the aliased L1 controller address. Same-block execution means L2 state updates before the L1 transaction completes.

Cross-Chain Oracles

Push data from L1 to L2 without oracle infrastructure:

solidity
contract PricePusher {
    function pushPrice(address feed, address l2Oracle) external payable {
        int256 price = IChainlinkFeed(feed).latestAnswer();
        ITransactor(TRANSACTOR).transact{value: msg.value}(
            l2Oracle,
            abi.encodeCall(IL2Oracle.updatePrice, (feed, price)),
            0, 100000, 1 gwei
        );
    }
}

The L2 oracle receives L1 Chainlink prices in the same block they’re published. No latency. No third-party relayers.


Execution Ordering

Transactor calls execute at the end of the Signet block. This matters for composability:

  1. Regular Signet transactions execute first
  2. Passage deposits are credited
  3. Transactor calls execute last

Your Transactor call can depend on state set earlier in the block, but earlier transactions can’t depend on Transactor results.


When to Use What

PatternUse CaseAssets Move?
enter()Deposit ETH → USDYes
enterToken()Deposit ERC-20 → Signet tokenYes
transact()L1 controls L2, oracles, governanceNo (gas only)

The Reverse Direction

Exiting Signet (L2→L1) uses the Orders system instead of a mirror Passage contract. Users post orders expressing “I want X on L1 for Y on L2,” and fillers compete to satisfy them.

This asymmetry is intentional:

  • Entry: Direct, trustless, immediate (Passage/Transactor)
  • Exit: Market-based, competitive, also immediate (Orders + Fillers)

See The Filler Economy for exit patterns.


Get Started

Contracts (Parmigiana Testnet):

  • Passage: 0x28524D2a753925Ef000C3f0F811cDf452C6256aF
  • Transactor: 0x0B4fc18e78c585687E01c172a1087Ea687943db9

Bindings:

bash
# Solidity
forge install https://github.com/init4tech/zenith

# Rust
cargo add signet-zenith

Docs:

Questions? Get in touch.

ESC

Start typing to search documentation...

Navigate Select ⌘K Open