Skip to main content

Swap within Signet

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 shared intro. The order model is identical – only the output chainId differs.

This page assumes you’ve set up your clients.

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 is the output chainId – use PARMIGIANA.rollupChainId instead of PARMIGIANA.hostChainId.

This page assumes you’ve completed the getting started 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.

This page assumes you’ve completed the getting started 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 is the chainId in the output – use the rollup chain ID instead of the host chain ID.

Source

This page assumes you’ve completed the getting started 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 or Rust SDK.

ESC

Start typing to search documentation...

Navigate Select ⌘K Open