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
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
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:
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
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
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:
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:
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:
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:
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
- Passage.sol source code
- ABIs available on the Parmigiana quickstart
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:
cast send $PASSAGE_ADDRESS \
"enter(address)" \
$YOUR_ADDRESS \
--value 1ether \
--rpc-url $HOST_RPC \
--private-key $PRIVATE_KEYVerify the deposit arrived on Signet:
cast balance $YOUR_ADDRESS --rpc-url $SIGNET_RPCEnter with ERC-20 tokens
Entering with tokens requires two transactions – approve, then bridge:
# 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_KEYFor error handling and common issues, see Troubleshooting.