Skip to main content

Deploy Your First Cross-Chain Contract

Get started with Pylon by deploying a contract on one of our demo appchains and read from their settlement layer.

Prerequisites

Step 1: Create Your Project

forge init my-pylon-app
cd my-pylon-app

Step 2: Configure Your Network

Add to your foundry.toml:
[rpc_endpoints]
pylon = "https://pylon.base-sepolia.spire.dev/v1/chain/2137/rpc"
Available Networks:
NetworkSettlement LayerRPC URLChain IDStatusAdd to Wallet
Pylon Base SepoliaBase Sepoliahttps://pylon.base-sepolia.spire.dev2137Testnet
Pylon Base MainnetBasehttps://pylon.base-mainnet.spire.dev7312Mainnet
Pylon Optimism SepoliaOptimism Sepoliahttps://pylon.optimism-sepolia.spire.dev2138Testnet
Pylon Celo MainnetCelohttps://pylon.celo-mainnet.spire.dev2139Mainnet

Step 3: Make a Direct Cross-Chain Call

Let’s start by understanding how cross-chain calls work. Pylon provides a Port Contract that reads from the settlement layer. Make a direct call to read WETH name from Base Sepolia:
cast call 0x0000000000000000000000000000000000000042 \
    "readSettlement(address,bytes)" \
    0x4200000000000000000000000000000000000006 \
    "$(cast calldata 'name()')" \
    --rpc-url pylon
What happened:
  1. Called the SettlementPort contract at 0x0000000000000000000000000000000000000042
  2. Told it to call name() on WETH (0x4200000000000000000000000000000000000006)
  3. The coordinator detected the cross-chain call, forwarded it to the settlement layer, and returned the result synchronously
  4. The result is encoded as bytes
Since the return type for the settlement contract being called is known, just specify the return type and it will automatically decode the result:
cast call 0x0000000000000000000000000000000000000042 \
    "readSettlement(address,bytes)(string)" \
    0x4200000000000000000000000000000000000006 \
    "$(cast calldata 'name()')" \
    --rpc-url pylon
This demonstrates synchronous composability - you can read from the settlement layer as if it were on the same chain.

Step 4: Create Forwarding Proxy (Better Developer Experience)

Making raw readSettlement calls works, but it’s verbose. We can create a forwarding proxy that makes settlement layer contracts callable using standard interfaces like ERC20. Create src/SettlementForwardingProxy.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import {ISettlementReader} from "./ISettlementReader.sol";

contract SettlementForwardingProxy {
    address public immutable SETTLEMENT_PORT;
    address public immutable IMPL;
    
    constructor(address _settlementPort, address _impl) {
        SETTLEMENT_PORT = _settlementPort;
        IMPL = _impl;
    }
    
    fallback() external payable {
        bytes memory result = ISettlementReader(SETTLEMENT_PORT).readSettlement(IMPL, msg.data);
        assembly {
            let ptr := add(result, 0x20)
            let len := mload(result)
            return(ptr, len)
        }
    }
    
    receive() external payable {
        bytes memory result = ISettlementReader(SETTLEMENT_PORT).readSettlement(IMPL, bytes(""));
        assembly {
            let ptr := add(result, 0x20)
            let len := mload(result)
            return(ptr, len)
        }
    }
}
Create src/ISettlementReader.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

interface ISettlementReader {
    function readSettlement(address target, bytes calldata callData) external view returns (bytes memory);
}
Deploy the proxy (replace with your actual settlement port address):
forge create src/SettlementForwardingProxy.sol:SettlementForwardingProxy \
    --rpc-url pylon \
    --broadcast \
    --private-key $PRIVATE_KEY \
    --constructor-args 0x0000000000000000000000000000000000000042 0x4200000000000000000000000000000000000006
Save the proxy address - you’ll use it in the next step. What this proxy does:
  • Constructor takes two addresses: the SettlementPort and a target contract (in this case WETH)
  • fallback() captures any function call and forwards it to readSettlement()
  • Returns the result as if the target contract were local
  • This lets you call settlement layer contracts using standard interfaces

Step 5: Create a Contract That Uses the Proxy

Now let’s create a contract that demonstrates using the proxy with standard interfaces: Create src/ProxyCaller.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

// Minimal ERC20 view interface for WETH
interface IERC20View {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
}

contract ProxyCaller {
    address public immutable PROXY;
    
    constructor(address _proxy) {
        PROXY = _proxy;
    }
    
    function nameViaProxy() external view returns (string memory) {
        return IERC20View(PROXY).name();
    }
    
    function symbolViaProxy() external view returns (string memory) {
        return IERC20View(PROXY).symbol();
    }
    
    function decimalsViaProxy() external view returns (uint8) {
        return IERC20View(PROXY).decimals();
    }
}
How this works:
  • ProxyCaller calls standard ERC20 functions on the PROXY address
  • The proxy’s fallback() forwards these calls to SettlementPort
  • SettlementPort reads from the settlement layer synchronously via priming transactions
  • Results return as if WETH were deployed on your appchain

Step 6: Deploy Contracts

Deploy the ProxyCaller contract:
forge create src/ProxyCaller.sol:ProxyCaller \
    --rpc-url pylon \
    --broadcast \
    --private-key $PRIVATE_KEY \
    --constructor-args <PROXY_ADDRESS>
Save the ProxyCaller address.

Step 7: Test Your Cross-Chain Calls

cast call <CALLER_ADDRESS> "nameViaProxy()(string)" \
    --rpc-url pylon

cast call <CALLER_ADDRESS> "symbolViaProxy()(string)" \
    --rpc-url pylon

cast call <CALLER_ADDRESS> "decimalsViaProxy()(uint8)" \
    --rpc-url pylon
Test getting WETH name, symbol, and decimals via the proxy. These calls demonstrate how you can interact with settlement layer contracts using standard interfaces, just like they were on the same chain.

What This Demonstrates

  1. Direct Pattern (Step 3): Made raw readSettlement calls to SettlementPort, showing the foundation of cross-chain reads
  2. Proxy Pattern (Step 4-5): Created a forwarding proxy that enables standard interface calls to settlement layer contracts from your appchain contracts
  3. Developer Experience: Now you can call settlement layer contracts using familiar Solidity syntax and interfaces

How Cross-Chain Calls Flow Through Pylon

How Cross-Chain Calls Flow Through Pylon The key insight is that Pylon’s coordinator detects these cross-chain calls, forwards them to the settlement layer, captures the results, and makes them available synchronously through the Port Contract. This is synchronous composability in action.

Next Steps