Skip to main content

Enter Signet

Move ETH and ERC-20 tokens from Ethereum to Signet via Passage

Passage is the bridge contract on Ethereum that moves assets into Signet. Deposits arrive on the rollup in the same block they’re submitted.

Passage has two entry points: one for ETH (sent as msg.value) and one for ERC-20 tokens (requires a prior token approval). Both take a rollupRecipient address that receives the tokens on Signet.

The Signet node watches for Enter and EnterToken events on the Passage contract. If the event fired, the deposit was processed – no additional confirmation is needed on the rollup side.

This page assumes you’ve set up your clients. See Parmigiana testnet for contract addresses.

Enter with ETH

typescript
import { passageAbi } from '@signet-sh/sdk/abi';
import { PARMIGIANA } from '@signet-sh/sdk/constants';
import { parseEther } from 'viem';

// 1. Deposit ETH into Signet via Passage on Ethereum
const hash = await hostWalletClient.writeContract({
  address: PARMIGIANA.hostPassage,
  abi: passageAbi,
  functionName: 'enter',
  args: [userAddress],
  value: parseEther('1'),
});

// 2. Wait for the Ethereum transaction to confirm
await hostPublicClient.waitForTransactionReceipt({ hash });

// 3. Tokens are on Signet, check the balance
const balance = await signetPublicClient.getBalance({ address: userAddress });
Without the SDK
typescript
import { parseEther } from 'viem';

const passageAbi = [
  {
    name: 'enter',
    type: 'function',
    stateMutability: 'payable',
    inputs: [{ name: 'rollupRecipient', type: 'address' }],
    outputs: [],
  },
] as const;

const hash = await hostWalletClient.writeContract({
  address: '0x28524D2a753925Ef000C3f0F811cDf452C6256aF',
  abi: passageAbi,
  functionName: 'enter',
  args: [userAddress],
  value: parseEther('1'),
});

await hostPublicClient.waitForTransactionReceipt({ hash });
const balance = await signetPublicClient.getBalance({ address: userAddress });

Enter with ERC-20 tokens

Approve Passage to spend the token, then call enterToken:

typescript
import { passageAbi } from '@signet-sh/sdk/abi';
import { PARMIGIANA } from '@signet-sh/sdk/constants';
import { getTokenAddress } from '@signet-sh/sdk/tokens';
import { erc20Abi } from 'viem';

// 1. Approve Passage to spend the token
await hostWalletClient.writeContract({
  address: tokenAddress,
  abi: erc20Abi,
  functionName: 'approve',
  args: [PARMIGIANA.hostPassage, amount],
});

// 2. Deposit into Signet
const hash = await hostWalletClient.writeContract({
  address: PARMIGIANA.hostPassage,
  abi: passageAbi,
  functionName: 'enterToken',
  args: [userAddress, tokenAddress, amount],
});

// 3. Wait, then check the rollup balance
await hostPublicClient.waitForTransactionReceipt({ hash });
const rollupToken = getTokenAddress('WETH', PARMIGIANA.rollupChainId, PARMIGIANA);
const balance = await signetPublicClient.readContract({
  address: rollupToken,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [userAddress],
});
Without the SDK
typescript
import { erc20Abi } from 'viem';

const enterTokenAbi = [
  {
    name: 'enterToken',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'rollupRecipient', type: 'address' },
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [],
  },
] as const;

await hostWalletClient.writeContract({
  address: tokenAddress,
  abi: erc20Abi,
  functionName: 'approve',
  args: ['0x28524D2a753925Ef000C3f0F811cDf452C6256aF', amount],
});

const hash = await hostWalletClient.writeContract({
  address: '0x28524D2a753925Ef000C3f0F811cDf452C6256aF',
  abi: enterTokenAbi,
  functionName: 'enterToken',
  args: [userAddress, tokenAddress, amount],
});

await hostPublicClient.waitForTransactionReceipt({ hash });
const balance = await signetPublicClient.readContract({
  address: rollupTokenAddress,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [userAddress],
});

For error handling and common issues, see Troubleshooting.

This page assumes you’ve set up your providers. See Parmigiana testnet for contract addresses.

Enter with ETH

rust
use signet_zenith::Passage;
use signet_constants::parmigiana;
use alloy::primitives::utils::parse_ether;

let passage = Passage::new(parmigiana::HOST_PASSAGE, &host_provider);

// Deposit ETH into Signet
let tx = passage
    .enter(rollup_recipient)
    .value(parse_ether("1")?)
    .send()
    .await?;

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

Enter with ERC-20 tokens

Approve Passage to spend the token, then call enterToken:

rust
use signet_zenith::Passage;
use signet_constants::parmigiana;

let passage = Passage::new(parmigiana::HOST_PASSAGE, &host_provider);

// 1. Approve Passage to spend the token
let approve_tx = token_contract
    .approve(parmigiana::HOST_PASSAGE, amount)
    .send()
    .await?;
approve_tx.get_receipt().await?;

// 2. Bridge tokens to Signet
let enter_tx = passage
    .enterToken(rollup_recipient, token_address, amount)
    .send()
    .await?;
enter_tx.get_receipt().await?;

For error handling and common issues, see Troubleshooting.

This page assumes you’ve completed the getting started setup. See Parmigiana testnet for the deployed Passage address.

Enter with ETH

Call enter on the Passage contract with ETH attached:

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

Passage passage = Passage(PASSAGE_ADDRESS);

// Deposit ETH into Signet -- rollupRecipient receives it on the rollup
passage.enter{value: 1 ether}(rollupRecipient);

Passage also accepts plain ETH transfers via receive(). Any ETH sent directly to the contract is bridged to msg.sender on Signet.

Enter with ERC-20 tokens

Approve Passage to spend the token, then call enterToken:

solidity
import {Passage} from "zenith/src/passage/Passage.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";

IERC20 token = IERC20(TOKEN_ADDRESS);
Passage passage = Passage(PASSAGE_ADDRESS);

// 1. Approve Passage to spend the token
token.approve(PASSAGE_ADDRESS, amount);

// 2. Bridge tokens to Signet
passage.enterToken(rollupRecipient, address(token), amount);

Contract integration

A minimal contract that wraps Passage for ETH and ERC-20 deposits:

solidity
contract YourContract {
    Passage public passage;
    IERC20 public token;

    constructor(address _passage, IERC20 _token) {
        passage = Passage(_passage);
        token = _token;
        token.approve(_passage, type(uint256).max);
    }

    function bridgeETH() external payable {
        passage.enter{value: msg.value}(msg.sender);
    }

    function bridgeToken(uint256 amount) external {
        token.transferFrom(msg.sender, address(this), amount);
        passage.enterToken(msg.sender, address(token), amount);
    }
}

The constructor approves Passage for the max token allowance so bridgeToken doesn’t need a per-call approval.

Source

For error handling and common issues, see Troubleshooting.

This page assumes you’ve completed the getting started setup. See Parmigiana testnet for contract addresses.

Enter with ETH

Send ETH directly to the Passage contract:

bash
cast send $PASSAGE_ADDRESS \
  "enter(address)" \
  $YOUR_ADDRESS \
  --value 1ether \
  --rpc-url $HOST_RPC \
  --private-key $PRIVATE_KEY

Verify the deposit arrived on Signet:

bash
cast balance $YOUR_ADDRESS --rpc-url $SIGNET_RPC

Enter with ERC-20 tokens

Entering with tokens requires two transactions – approve, then bridge:

bash
# 1. Approve Passage to spend tokens
cast send $TOKEN_ADDRESS \
  "approve(address,uint256)" \
  $PASSAGE_ADDRESS $AMOUNT \
  --rpc-url $HOST_RPC \
  --private-key $PRIVATE_KEY

# 2. Bridge tokens to Signet
cast send $PASSAGE_ADDRESS \
  "enterToken(address,address,uint256)" \
  $YOUR_ADDRESS $TOKEN_ADDRESS $AMOUNT \
  --rpc-url $HOST_RPC \
  --private-key $PRIVATE_KEY

For error handling and common issues, see Troubleshooting.

ESC

Start typing to search documentation...

Navigate Select ⌘K Open