Passage Integration Guide
The Passage contract
helps move assets from Ethereum to Signet instantly.
Signet nodes listen for Passage
events, and mint tokens on Signet in the
same block.
Consult the Pecorino Quickstart
guide for contract addresses and RPC
endpoints.
Setup
- The Passage ABI can be downloaded here: Passage ABI.
$ curl -O https://signet.sh/docs/abis/Passage.abi.json
- The
Passage Contract
is also open source.$ forge install https://github.com/init4tech/zenith
- Rust alloy bindings are available via the
signet-bindings
crate.$ cargo add signet-bindings
Overview
Passage.sol
exposes a simple interface:
enter(address rollupRecipient)
- move ETH to Signet.
fallback
andreceive
- also move ETH to Signet, withmsg.sender
as recipient.
enterToken(address rollupRecipient, address token, uint256 amount)
- move ERC-20 tokens to Signet
- requires prior
approve
of Passage to spend tokens
Passage can be observed using the Enter
and EnterToken
events. The Enter
event is fired whenever ETH is bridging to Signet, and EnterToken
is fired
when ERC-20 tokens are bridged. The Signet node listens for these events directly, so if the event fired, the tokens were bridged.
Supported Assets
Signet currently supports bridging the following assets from Ethereum:
- ETH
- USDC
- USDT
- WBTC
Custom assets can be created on Signet but cannot be bridged from Ethereum. Contact the Signet team to add support for additional assets.
Code Examples
Solidity
Passage is available as a Solidity contract or ABI. See the Setup section above.
1contract YourContract {
2 Passage public passage;
3 IERC20 public token;
4
5 constructor(address _passage, IERC20 _token) {
6 passage = Passage(_passage);
7 token = _token;
8 token.approve(_passage, type(uint256).max);
9 }
10
11 // Enter on behalf of a user
12 function bridgeETH() external payable {
13 passage.enter{value: msg.value}(msg.sender);
14 }
15
16 // Move ERC-20 tokens to Signet
17 function bridgeToken(uint256 amount) external {
18 token.transferFrom(msg.sender, address(this), amount);
19 passage.enterToken(address(token), msg.sender);
20 }
21}
Cast
To enter the rollup, Send ETH directly to the Passage contract
:
1# Using cast
2cast send $PASSAGE_ADDRESS \
3 --value 1ether \
4 --rpc-url $HOST_CHAIN_RPC \
5 --private-key $PRIVATE_KEY
Entering with tokens requires multiple transactions:
1# 1. Approve Passage
2cast send $TOKEN_ADDRESS \
3 "approve(address,uint256)" \
4 $PASSAGE_ADDRESS, $AMOUNT \
5 --rpc-url $HOST_CHAIN_RPC \
6 --private-key $PRIVATE_KEY
7
8# 2. Call Passage to bridge tokens
9# See Passage.sol for the exact function name and parameters
10cast send $PASSAGE_ADDRESS \
11 "enterTokens(address,address,uint256)" \
12 $SIGNET_RECIPIENT, $TOKEN_ADDRESS, $AMOUNT \
13 --rpc-url $HOST_CHAIN_RPC \
14 --private-key $PRIVATE_KEY
Typescript
1// Using ethers.js
2const tx = await signer.sendTransaction({
3 to: PASSAGE_ADDRESS,
4 value: ethers.parseEther("1.0"),
5});
6await tx.wait();
Listen for events:
1import { ethers } from "ethers";
2
3const provider = new ethers.JsonRpcProvider("https://eth.llamarpc.com");
4const passage = new ethers.Contract(
5 PASSAGE_ADDRESS,
6 PASSAGE_ABI, // Get ABI from contract source
7 provider
8);
9
10// Listen for events
11// Event names and signatures from Passage.sol
12passage.on("Enter", (...args) => {
13 console.log("ETH bridged:", args);
14});
15
16passage.on("EnterToken", (...args) => {
17 console.log("Token bridged:", args);
18});
Rust
1let passage = PassageContract::new(
2 signet_constants::pecorino::HOST_PASSAGE,
3 provider.clone(),
4);
5
6// Enter the rollup from your EOA
7passage.enter().value(eth_in_wei).send(rollup_recipient).await?;
Check Passage.sol for the exact event signatures.
Next Steps
- Send a rollup transaction using the Transactor contract.