Skip to main content
This guide will help you build, test, and understand the Across Protocol smart contracts.

Prerequisites

Before you begin, ensure you have the following installed:
  • Node.js: Version 22.18.0 or higher (version 20.17 recommended if you encounter build issues)
  • Foundry: Required for building and testing Solidity contracts
  • Anchor: Required if working with SVM (Solana) programs
If you encounter build issues on initial yarn command, try downgrading to Node 20.17 using nvm use 20.17. If you’ve never used Anchor before, run avm use latest.

Installation

Clone the repository and install dependencies:
npm install

Build the contracts

The project supports building with both Foundry and Hardhat:
1

Build all contracts

Compile Solidity and Rust code, generate TypeScript outputs:
yarn build
2

Build EVM contracts only

Use Foundry or Hardhat to compile Solidity contracts:
forge build
3

Build SVM programs (optional)

If working with Solana programs:
yarn build-svm

Run tests

Across uses Foundry for new tests and Hardhat for legacy tests. The project is migrating to Foundry as the primary testing framework. Run all EVM tests with Foundry:
yarn test-evm-foundry
Always use FOUNDRY_PROFILE=local-test when running Foundry tests in this repository. The yarn test-evm-foundry command sets this automatically.

Run specific tests

FOUNDRY_PROFILE=local-test forge test --match-test testDeposit

Hardhat tests (legacy)

Run legacy Hardhat tests:
yarn test-evm-hardhat

Gas analysis

Run tests with gas reporting:
yarn test:report-gas

SVM tests

Test Solana programs:
yarn test-svm

Example: Deposit flow

Here’s how a user deposits tokens on a SpokePool:
contracts/SpokePool.sol
function depositV3(
    address depositor,
    address recipient,
    address inputToken,
    address outputToken,
    uint256 inputAmount,
    uint256 outputAmount,
    uint256 destinationChainId,
    address exclusiveRelayer,
    uint32 quoteTimestamp,
    uint32 fillDeadline,
    uint32 exclusivityParameter,
    bytes calldata message
) public payable override {
    deposit(
        depositor.toBytes32(),
        recipient.toBytes32(),
        inputToken.toBytes32(),
        outputToken.toBytes32(),
        inputAmount,
        outputAmount,
        destinationChainId,
        exclusiveRelayer.toBytes32(),
        quoteTimestamp,
        fillDeadline,
        exclusivityParameter,
        message
    );
}

Key parameters

  • depositor: Account credited with the deposit who can speed up the transfer
  • recipient: Account receiving funds on the destination chain
  • inputToken: Token locked on origin chain (can be native token if wrapped)
  • outputToken: Token sent to recipient on destination chain
  • inputAmount: Amount deposited and locked
  • outputAmount: Amount relayer sends (fee = inputAmount - outputAmount)
  • destinationChainId: Target chain for the transfer
  • quoteTimestamp: HubPool timestamp used to determine system fees
  • fillDeadline: Deadline for relayers to fill the deposit

Example: Relayer fill

Relayers fill deposits on the destination chain:
contracts/SpokePool.sol
// Relayers call fillRelay() or fillRelayWithUpdatedDeposit()
// to fulfill user deposits on the destination chain.
// The relayer fronts the output tokens and receives a refund
// on their chosen repayment chain after the bundle is validated.

Deploy contracts

Across supports deployment via Foundry and Hardhat.

Deploy with Foundry

forge script script/001DeployHubPool.s.sol:DeployHubPool \
  --rpc-url ethereum \
  --broadcast \
  --verify \
  -vvvv

Deploy with Hardhat

NODE_URL_1=https://mainnet.infura.com/xxx \
  yarn hardhat deploy --tags HubPool --network mainnet

Verify contracts

Verify deployed contracts on Etherscan:
ETHERSCAN_API_KEY=XXX \
  yarn hardhat etherscan-verify \
  --network mainnet \
  --license AGPL-3.0 \
  --force-license \
  --solc-input

Lint and format

Maintain code quality with linting:
yarn lint-fix

Project structure

contracts/           # Smart contract source files
  chain-adapters/    # L1 chain adapters for bridging
  interfaces/        # Interface definitions
  libraries/         # Shared libraries
test/evm/
  foundry/           # Foundry tests (.t.sol)
    local/           # Local unit tests
    fork/            # Fork tests
  hardhat/           # Legacy Hardhat tests (.ts)
script/              # Foundry deployment scripts (.s.sol)
  utils/             # Script utilities (Constants.sol)
lib/                 # External dependencies (git submodules)

Key contracts

  • HubPool.sol (contracts/HubPool.sol): Central L1 contract managing liquidity and root bundle validation
  • SpokePool.sol (contracts/SpokePool.sol): Base contract for all L2 spoke pools
  • Arbitrum_SpokePool.sol: Arbitrum-specific spoke pool with custom admin verification
  • Optimism_SpokePool.sol: Optimism-specific spoke pool (and OP Stack chains)
  • Chain adapters (contracts/chain-adapters/): Bridge tokens and messages to each L2

Next steps

Architecture guide

Learn about the hub-and-spoke model and protocol flow

HubPool contract

Explore the central L1 contract managing liquidity

SpokePool contract

Understand how deposits and fills work on L2s

Testing guide

Write tests for new features and adapters

Additional resources

  • Audit reports: Security audit reports by OpenZeppelin
  • GitHub repository: Full source code
  • Deployed contracts: Check broadcast/deployed-addresses.json in the repository
For detailed deployment instructions for specific chains (including ZKSync and Solana), see the README.md in the repository.

Build docs developers (and LLMs) love