> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spire.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Onchain

> Deploy contracts, send transactions, and build applications on Pylon appchains using standard Ethereum tooling

## Synchronous Cross-Chain Composability

Pylon appchains already support **synchronous reads** from their settlement layer: your contracts can call settlement layer view functions and use the results in the same transaction. The full concept and roadmap live in **[Synchronous Composability](/pylon/synchronous-composability)**—this section focuses on how to use it in practice.

**Today:**

* ✅ Read settlement layer state atomically (no async polling or bridges)
* ✅ Use forwarding proxies or the SettlementPort directly for developer ergonomics

**Coming soon:**

* 🔄 Synchronous writes and broader multi-chain coordination (see the [roadmap section](/pylon/synchronous-composability#in-the-works))

To integrate reads in your appchain:

* Follow the [Quickstart](/pylon/quickstart) for a step-by-step proxy deployment example
* Use forwarding proxies or call SettlementPort directly (see examples below)
* Lean on pre-deployed [utility contracts](#pre-deployed-utility-contracts) for deterministic addresses and simplified setup

For architectural details, see [SettlementPort](/pylon/architecture#port-contracts), [forwarding proxies](/pylon/architecture#forwarding-proxy-contracts), and the coordinator overview in the [Architecture](/pylon/architecture) page.

## Standard Ethereum RPC Compatibility

Pylon appchains expose **full Ethereum JSON-RPC 2.0 compatible endpoints**. The RPC endpoint received during [chain setup](/pylon/chain-setup) functions identically to any other Ethereum-compatible chain.

**What this means:**

* Standard RPC methods (`eth_sendRawTransaction`, `eth_getTransactionReceipt`, `eth_call`, etc.) work as expected
* All Ethereum development tools are compatible (Foundry, Hardhat, ethers.js, web3.js, etc.)
* Wallets connect using standard network configuration
* No special integrations or custom SDKs required

The RPC endpoint uses the same protocol and response formats as Ethereum mainnet, Base, Arbitrum, and other EVM chains.

## Deploying Contracts

Deploy contracts using [`forge create`](https://book.getfoundry.sh/reference/forge/forge-create). Configure your RPC endpoint in `foundry.toml`:

```toml theme={null}
[rpc_endpoints]
pylon = "https://pylon.{settlement-layer}.spire.dev/v1/chain/{chain-id}/rpc"
```

Then deploy:

```bash theme={null}
forge create MyContract --rpc-url pylon --private-key $PRIVATE_KEY --broadcast
```

For complex deployments with constructor arguments or multi-step workflows, use [`forge script`](https://book.getfoundry.sh/reference/forge/forge-script).

## Sending Transactions

Send transactions using standard methods and libraries. Examples below show the same operation using different tools: Foundry's [`cast send`](https://book.getfoundry.sh/reference/cast/cast-send), ethers.js, and web3.js.

<CodeGroup>
  ```bash Cast theme={null}
  cast send {CONTRACT_ADDRESS} \
      "functionName(uint256)" \
      123 \
      --rpc-url pylon \
      --private-key $PRIVATE_KEY \
      --broadcast
  ```

  ```javascript ethers.js theme={null}
  import { ethers } from 'ethers';

  const provider = new ethers.JsonRpcProvider('https://pylon.{settlement-layer}.spire.dev/v1/chain/{chain-id}/rpc');
  const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

  const contract = new ethers.Contract(contractAddress, abi, wallet);
  const tx = await contract.functionName(123);
  await tx.wait();
  ```

  ```javascript web3.js theme={null}
  const Web3 = require('web3');
  const web3 = new Web3('https://pylon.{settlement-layer}.spire.dev/v1/chain/{chain-id}/rpc');

  const contract = new web3.eth.Contract(abi, contractAddress);
  const tx = await contract.methods.functionName(123).send({
    from: account,
    gas: 100000
  });
  ```
</CodeGroup>

## Interacting with Contracts

Call contract functions using standard RPC methods.

### View Calls

Read contract state with [`cast call`](https://book.getfoundry.sh/reference/cast/cast-call):

```bash theme={null}
cast call {CONTRACT_ADDRESS} \
    "functionName()(uint256)" \
    --rpc-url pylon
```

### State-Changing Calls

Call functions that modify state using [`cast send`](https://book.getfoundry.sh/reference/cast/cast-send):

```bash theme={null}
cast send {CONTRACT_ADDRESS} \
    "setValue(uint256)" \
    456 \
    --rpc-url pylon \
    --private-key $PRIVATE_KEY \
    --broadcast
```

## Testing Your Contracts

The **[simplest-pylon-demo](https://github.com/spire-labs/simplest-pylon-demo)** repository provides working examples of contracts and unit tests that demonstrate cross-chain calling patterns on Pylon.

### Testing Patterns

Unit tests for contracts that interact with the settlement layer should mock the cross-chain infrastructure. Two mocking approaches are available:

* **Mock the SettlementForwardingProxy** - Implement the same interface your application uses (e.g., `IERC20`) with setters to configure mock responses. This is the standard contract mocking pattern.
* **Mock the SettlementPort** - Mock the [SettlementPort](/pylon/architecture#port-contracts) contract to test the full proxy code path. The repository demonstrates this approach.

Follow standard Solidity mocking best practices—implement the interface with setters to configure mock behavior.

Run tests using [`forge test`](https://book.getfoundry.sh/reference/forge/forge-test):

```bash theme={null}
forge test -vvv
```

## Debugging Transactions

When transactions fail or the RPC rejects them during simulation, use these debugging techniques.

### Simulate Calls to Inspect Errors

The RPC often rejects transactions that would fail during simulation. Use [`cast call`](https://book.getfoundry.sh/reference/cast/cast-call) to see the error that would occur:

```bash theme={null}
cast call {CONTRACT_ADDRESS} \
    "functionName(params)(returnType)" \
    {ARGUMENTS} \
    --rpc-url pylon
```

This executes the call as a view function and returns either the result or the error message. This helps identify issues before submitting transactions.

For cross-chain reads, test the settlement port directly (using [`cast calldata`](https://book.getfoundry.sh/reference/cast/cast-calldata) to encode function calls):

```bash theme={null}
cast call 0x0000000000000000000000000000000000000042 \
    "readSettlement(address,bytes)(string)" \
    {TARGET_CONTRACT} \
    "$(cast calldata 'functionName()')" \
    --rpc-url pylon
```

If this returns an error, the issue is with the cross-chain call itself rather than contract logic.

### Debug Failed Transactions with Cast Run

When a transaction fails after submission, use [`cast run`](https://book.getfoundry.sh/reference/cast/cast-run) to trace execution and identify the failure point:

```bash theme={null}
cast run {TX_HASH} \
    --rpc-url pylon \
    -vvv
```

The verbose flags (`-vvv`) show detailed execution traces, including:

* Opcode-level execution
* Storage changes
* Revert reasons

This is especially useful for debugging cross-chain calls that revert without clear error messages.

### Verify Settlement Layer Connectivity

If cross-chain calls consistently fail, verify the coordinator can reach the settlement layer:

```bash theme={null}
cast call 0x0000000000000000000000000000000000000042 \
    "readSettlement(address,bytes)" \
    {KNOWN_CONTRACT} \
    "$(cast calldata 'name()')" \
    --rpc-url pylon
```

Use a known contract address from the settlement layer to test connectivity. If this fails, the coordinator may be down or the settlement layer unreachable.

### Inspect Transaction Receipts

For successfully submitted transactions, inspect the receipt for execution details using [`cast receipt`](https://book.getfoundry.sh/reference/cast/cast-receipt):

```bash theme={null}
cast receipt {TX_HASH} --rpc-url pylon
```

Check logs for cross-chain call execution events and verify the transaction actually executed as expected.

## Direct Settlement Layer Reads

You can call the SettlementPort directly using the `l2Read` function to read from settlement layer contracts. This provides more control over calldata encoding and error handling than forwarding proxies.

### Basic Usage

The `l2Read` function is available through the [`SettlementPort`](/pylon/architecture#port-contracts) contract at address `0x0000000000000000000000000000000000000042`:

```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

interface IL2Reader {
    function l2Read(address target, bytes calldata data) external view returns (bytes memory);
}

contract MyAppContract {
    address public immutable settlementPort = 0x0000000000000000000000000000000000000042;

    function getL2TokenBalance(address tokenContract, address account) external view returns (uint256) {
        // Encode the balanceOf function call
        bytes memory data = abi.encodeWithSignature("balanceOf(address)", account);

        // Perform the L2 read
        bytes memory result = IL2Reader(settlementPort).l2Read(tokenContract, data);

        // Decode the result
        return abi.decode(result, (uint256));
    }
}
```

### Key Points

* **View Function**: `l2Read` is a `view` function, so it doesn't modify state
* **Synchronous**: The read appears synchronous to your contract, but the data is [pre-fetched by the sequencer](/pylon/architecture#priming-transactions)
* **Error Handling**: The function will revert if the L2 call fails—use `try/catch` for graceful error handling

### Error Handling

For graceful error handling when L2 reads might fail:

```solidity theme={null}
function processL2Data(address l2Contract) external view returns (bool) {
    try IL2Reader(settlementPort).l2Read(l2Contract, abi.encodeWithSignature("getStatus()")) returns (bytes memory result) {
        bool status = abi.decode(result, (bool));
        return status;
    } catch {
        // Handle L2 read failure
        return false;
    }
}
```

## Pre-Deployed Utility Contracts

Pylon can pre-deploy utility contracts at deterministic addresses on your appchain. These contracts enable common development patterns without requiring deployment. Request these contracts or others during [chain setup](/pylon/chain-setup) or through the [Spire contact form](https://www.spire.dev/contact).

### Available Contracts

The following utility contracts can be pre-deployed on request:

| Contract                             | Default Address                              | Purpose                                             |
| ------------------------------------ | -------------------------------------------- | --------------------------------------------------- |
| **SettlementForwardingProxyFactory** | `0x0000000000000000000000000000000000000043` | Deploy proxies for cross-chain reads                |
| **CREATE2Factory**                   | `0x0000000000000000000000000000000000000044` | Deterministic contract deployments                  |
| **Multicall**                        | `0x0000000000000000000000000000000000000045` | Batch multiple calls in a single transaction        |
| **MinimalProxyFactory**              | `0x0000000000000000000000000000000000000046` | Deploy minimal proxies (EIP-1167) for cheap cloning |

You can request these contracts to be pre-deployed at the default addresses listed above, or specify custom addresses for your appchain. If you don't request pre-deployment, you can deploy these contracts yourself at any address.

### SettlementForwardingProxyFactory

The SettlementForwardingProxyFactory deploys forwarding proxies that enable clean interface-based access to settlement layer contracts. Each proxy forwards calls to a specified settlement layer contract through the SettlementPort.

**Interface:**

```solidity theme={null}
interface ISettlementForwardingProxyFactory {
    function createProxy(address settlementAddress) external returns (address proxy);
    function computeProxyAddress(address settlementAddress, bytes32 salt) external view returns (address);
}
```

The factory uses CREATE2 internally, enabling deterministic proxy addresses. Use `computeProxyAddress` to calculate the proxy address before deployment.

For complete examples, see the **[simplest-pylon-demo](https://github.com/spire-labs/simplest-pylon-demo)** repository which demonstrates proxy deployment and usage patterns.

### CREATE2Factory

Enables deterministic contract deployments using the [CREATE2 opcode](https://eips.ethereum.org/EIPS/eip-1014). Contracts can be deployed to addresses computed in advance based on the deployer address, salt, and bytecode hash.

**Interface:**

```solidity theme={null}
interface ICREATE2Factory {
    function deploy(bytes32 salt, bytes memory bytecode) external returns (address);
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) external view returns (address);
}
```

Use `computeAddress` to calculate the deployment address before calling `deploy`. The factory uses CREATE2 internally, ensuring that contracts with the same salt and bytecode always deploy to the same address.

### Multicall

Batches multiple function calls into a single transaction, reducing gas costs by combining operations. Useful for aggregating multiple cross-chain reads or state queries in a single transaction.

**Interface:**

```solidity theme={null}
interface IMulticall {
    struct Call {
        address target;
        bytes callData;
    }
    
    function aggregate(Call[] memory calls) external returns (uint256 blockNumber, bytes[] memory returnData);
    function tryAggregate(bool requireSuccess, Call[] memory calls) external returns (uint256 blockNumber, Result[] memory returnData);
}

struct Result {
    bool success;
    bytes returnData;
}
```

**Usage:**

Call multiple contract functions in a single transaction. The `aggregate` function reverts if any call fails, while `tryAggregate` allows partial failures when `requireSuccess` is `false`.

```solidity theme={null}
// Example: Batch multiple cross-chain reads
IMulticall.Call[] memory calls = new IMulticall.Call[](2);
calls[0] = IMulticall.Call({
    target: settlementProxy1,
    callData: abi.encodeWithSignature("balanceOf(address)", user)
});
calls[1] = IMulticall.Call({
    target: settlementProxy2,
    callData: abi.encodeWithSignature("totalSupply()")
});

(, bytes[] memory results) = IMulticall(MULTICALL_ADDRESS).aggregate(calls);
uint256 balance = abi.decode(results[0], (uint256));
uint256 supply = abi.decode(results[1], (uint256));
```

### MinimalProxyFactory

Deploys [EIP-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxies that forward calls to an implementation contract via `delegatecall`. Each proxy maintains its own storage while sharing the implementation's code, enabling cheap deployment of many instances. See [EIP-1167](https://eips.ethereum.org/EIPS/eip-1167) and [OpenZeppelin Clones](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Clones) for details.

## Next Steps

* **[Chain Management →](/pylon/chain-management)** - Monitor and maintain your chain
* **[Connect Users →](/pylon/connect-users)** - Configure wallet integration
* **[Architecture →](/pylon/architecture)** - Understand how Pylon works under the hood
