---
title: "Swap within Signet"
url: "/docs/build-on-signet/transfers/swap-within-signet/index.md"
description: "Trade tokens on Signet without exiting to Ethereum"
---
When you want a different token on Signet without crossing to Ethereum, create an order where both input and output are on the rollup chain. The order stays entirely within Signet and settles without touching Ethereum at all.

For order structure, pricing, and filler economics, see the [Exit Signet](/docs/build-on-signet/transfers/exit-signet/index.md) shared intro. The order model is identical -- only the output `chainId` differs.

## TypeScript

This page assumes you've [set up your clients](/docs/build-on-signet/getting-started/index.md) .
## On-chain swap (costs gas) Call initiate on RollupOrders with the rollup chain ID for both input and output. This example swaps 1 ETH for WETH on Signet:
```typescript import { rollupOrdersAbi } from '@signet-sh/sdk/abi'; import { PARMIGIANA } from '@signet-sh/sdk/constants'; import { getTokenAddress } from '@signet-sh/sdk/tokens'; import { parseEther } from 'viem'; const rollupWeth = getTokenAddress('WETH', PARMIGIANA.rollupChainId, PARMIGIANA); const inputAmount = parseEther('1'); const outputAmount = (inputAmount * 995n) / 1000n; // 50bps to fillers const hash = await signetWalletClient.writeContract({ address: PARMIGIANA.rollupOrders, abi: rollupOrdersAbi, functionName: 'initiate', args: [ BigInt(Math.floor(Date.now() / 1000) + 60), // 1 minute deadline [{ token: '0x0000000000000000000000000000000000000000', amount: inputAmount }], [{ token: rollupWeth, amount: outputAmount, recipient: userAddress, chainId: Number(PARMIGIANA.rollupChainId) }], ], value: inputAmount, }); ``` ## Gasless swap (Permit2) Build an UnsignedOrder with the rollup chain ID on the output. The user signs a message instead of a transaction:
```typescript import { UnsignedOrder, randomNonce } from '@signet-sh/sdk/signing'; import { PARMIGIANA } from '@signet-sh/sdk/constants'; import { getTokenAddress } from '@signet-sh/sdk/tokens'; import { ensurePermit2Approval } from '@signet-sh/sdk/permit2'; import { createTxCacheClient } from '@signet-sh/sdk/client'; import { parseEther } from 'viem'; const weth = getTokenAddress('WETH', PARMIGIANA.rollupChainId, PARMIGIANA); const usdc = getTokenAddress('USDC', PARMIGIANA.rollupChainId, PARMIGIANA); // 1. One-time: let Permit2 spend your WETH await ensurePermit2Approval(signetWalletClient, signetPublicClient, { token: weth, owner: userAddress, amount: parseEther('1'), }); // 2. Build and sign -- note rollupChainId on the output const signed = await UnsignedOrder.new() .withInput(weth, parseEther('1')) .withOutput(usdc, 2985_000000n, userAddress, Number(PARMIGIANA.rollupChainId)) .withDeadline(BigInt(Math.floor(Date.now() / 1000) + 60)) .withNonce(randomNonce()) .withChain({ chainId: PARMIGIANA.rollupChainId, orderContract: PARMIGIANA.rollupOrders }) .sign(signetWalletClient); // 3. Submit to the tx-cache const txCache = createTxCacheClient(PARMIGIANA.txCacheUrl); await txCache.submitOrder(signed); ``` The only difference from an [exit order](/docs/build-on-signet/transfers/exit-signet/index.md) is the output chainId – use PARMIGIANA.rollupChainId instead of PARMIGIANA.hostChainId.

## Rust

This page assumes you've completed the [getting started](/docs/build-on-signet/getting-started/index.md) setup.
## On-chain swap Call initiate on RollupOrders with the rollup chain ID for the output. This keeps the swap entirely within Signet:
```rust use signet_zenith::RollupOrders; use signet_constants::parmigiana; let orders = RollupOrders::new(parmigiana::ROLLUP_ORDERS, &signet_provider); let input_amount = U256::from(1_000_000_000_000_000_000u128); // 1 ETH let output_amount = input_amount * U256::from(995) / U256::from(1000); let deadline = U256::from(std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH)?.as_secs() + 60); let tx = orders .initiate( deadline, vec![Input { token: Address::ZERO, amount: input_amount }], vec![Output { token: parmigiana::RU_WETH, amount: output_amount, recipient: your_address, chainId: parmigiana::ROLLUP_CHAIN_ID, // stays on rollup }], ) .value(input_amount) .send() .await?; tx.get_receipt().await?; ``` ## Gasless swap (Permit2) Sign a gasless order with the rollup chain ID on the output:
```rust use signet_types::signing::order::UnsignedOrder; use signet_constants::parmigiana; use signet_tx_cache::TxCache; let signed = UnsignedOrder::default() .with_input(parmigiana::RU_WETH, input_amount) .with_output( parmigiana::RU_USDC, output_amount, your_address, parmigiana::ROLLUP_CHAIN_ID, // stays on rollup ) .with_chain(parmigiana::system_constants()) .with_nonce(permit2_nonce) .sign(&signer).await?; let tx_cache = TxCache::parmigiana(); tx_cache.forward_order(signed).await?; ``` The only difference from an exit order is the output chain ID – use the rollup chain ID instead of the host chain ID.

## Solidity

This page assumes you've completed the [getting started](/docs/build-on-signet/getting-started/index.md) setup.
## On-chain swap via initiate Call initiate on RollupOrders with the rollup chain ID on the output. The swap stays entirely within Signet:
```solidity import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; contract OnChainSwap { RollupOrders public orders; uint32 public constant ROLLUP_CHAIN_ID = 88888; // Parmigiana rollup constructor(address _orders) { orders = RollupOrders(_orders); } /// @notice Swap native ETH for WETH on Signet function swap(address weth, uint256 outputAmount) external payable { RollupOrders.Input[] memory ins = new RollupOrders.Input[](1); ins[0] = RollupOrders.Input(address(0), msg.value); RollupOrders.Output[] memory outs = new RollupOrders.Output[](1); outs[0] = RollupOrders.Output( weth, outputAmount, msg.sender, ROLLUP_CHAIN_ID // stays on rollup ); orders.initiate{value: msg.value}( block.timestamp + 60, ins, outs ); } } ``` The only difference from an [exit order](/docs/build-on-signet/transfers/exit-signet/index.md) is the chainId in the output – use the rollup chain ID instead of the host chain ID.
## Source [RollupOrders.sol](https://github.com/init4tech/zenith/blob/main/src/orders/RollupOrders.sol) source code ABIs available on the [Parmigiana quickstart](/docs/build-on-signet/parmigiana/index.md#abis) 

## Terminal

This page assumes you've completed the [getting started](/docs/build-on-signet/getting-started/index.md) setup.
## On-chain swap via initiate Call initiate on RollupOrders with the rollup chain ID on the output. This example swaps 1 native ETH for WETH, staying entirely within Signet:
```bash cast send `0x000000000000007369676e65742d6f7264657273` \ "initiate(uint256,(address,uint256)[],(address,uint256,address,uint32)[])" \ $(( $(date +%s) + 60 )) \ "[(0x0000000000000000000000000000000000000000,1000000000000000000)]" \ "[($ROLLUP_WETH,995000000000000000,$YOUR_ADDRESS,88888)]" \ --value 1ether \ --rpc-url $SIGNET_RPC \ --private-key $PRIVATE_KEY ``` The arguments:
deadline: Unix timestamp (60 seconds from now) inputs: (token, amount) tuples. address(0) means native ETH, sent via --value outputs: (token, amount, recipient, chainId) tuples. 88888 is Parmigiana's rollup chain ID The key difference from an exit order is chainId – use the rollup chain ID (88888) instead of the host chain ID (3151908).
The gasless Permit2 flow requires EIP-712 typed data signing, which cast doesn't support. For gasless swaps, use the [TypeScript](/docs/build-on-signet/transfers/swap-within-signet/index.md) or [Rust](/docs/build-on-signet/transfers/swap-within-signet/index.md) SDK.

