Skip to main content
Foundry is a fast, portable toolkit for Ethereum development written in Rust. Tempo is fully compatible with Foundry, enabling you to develop, test, and deploy smart contracts just like on Ethereum.

Installation

Install the Tempo-compatible Foundry fork:
curl -L https://foundry.paradigm.xyz | bash
foundryup
Tempo works with standard Foundry. A Tempo-specific fork may be available in the future with additional features.
Verify installation:
forge --version
cast --version

Quick Start

Create a New Project

Initialize a Foundry project:
forge init my-tempo-project
cd my-tempo-project
This creates:
  • src/: Smart contract sources
  • test/: Test files
  • script/: Deployment scripts
  • foundry.toml: Configuration file

Configure for Tempo

Update foundry.toml:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

[rpc_endpoints]
tempo-testnet = "https://rpc.moderato.tempo.xyz"

[etherscan]
tempo-testnet = { key = "${TEMPO_EXPLORER_KEY}", url = "https://explore.tempo.xyz/api" }

Interacting with Tempo

Get Testnet Funds

Use cast to fund an address:
cast rpc tempo_fundAddress <ADDRESS> --rpc-url https://rpc.moderato.tempo.xyz
Example:
cast rpc tempo_fundAddress 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb \
  --rpc-url https://rpc.moderato.tempo.xyz
This funds the address with testnet stablecoins.

Query Token Balance

Check TIP-20 token balance:
cast call 0x20c0000000000000000000000000000000000001 \
  "balanceOf(address)(uint256)" \
  0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb \
  --rpc-url tempo-testnet

Send Transfer

Send a TIP-20 transfer:
cast send 0x20c0000000000000000000000000000000000001 \
  "transfer(address,uint256)" \
  0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb \
  100000000 \
  --rpc-url tempo-testnet \
  --private-key $PRIVATE_KEY

Get Block Number

Query the current block:
cast block-number --rpc-url tempo-testnet

Get Transaction Receipt

Check transaction status:
cast receipt <TX_HASH> --rpc-url tempo-testnet

Developing Contracts

Write a Contract

Create src/MyToken.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "forge-std/Script.sol";

contract MyToken {
    mapping(address => uint256) public balances;

    event Transfer(address indexed from, address indexed to, uint256 amount);

    function mint(address to, uint256 amount) external {
        balances[to] += amount;
        emit Transfer(address(0), to, amount);
    }

    function transfer(address to, uint256 amount) external {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        balances[to] += amount;
        emit Transfer(msg.sender, to, amount);
    }
}

Write Tests

Create test/MyToken.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/MyToken.sol";

contract MyTokenTest is Test {
    MyToken public token;
    address public alice = address(0x1);
    address public bob = address(0x2);

    function setUp() public {
        token = new MyToken();
    }

    function testMint() public {
        token.mint(alice, 1000);
        assertEq(token.balances(alice), 1000);
    }

    function testTransfer() public {
        token.mint(alice, 1000);
        
        vm.prank(alice);
        token.transfer(bob, 500);
        
        assertEq(token.balances(alice), 500);
        assertEq(token.balances(bob), 500);
    }

    function testTransferInsufficientBalance() public {
        token.mint(alice, 100);
        
        vm.prank(alice);
        vm.expectRevert("Insufficient balance");
        token.transfer(bob, 500);
    }
}

Run Tests

Execute tests locally:
forge test
Run with verbosity:
forge test -vvv
Run specific tests:
forge test --match-test testTransfer

Test Against Tempo Fork

Fork Tempo testnet for testing:
forge test --fork-url https://rpc.moderato.tempo.xyz
This runs tests against live Tempo state, including:
  • Real TIP-20 tokens
  • Deployed contracts
  • Current chain state

Deploying Contracts

Create Deployment Script

Create script/Deploy.s.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "../src/MyToken.sol";

contract DeployScript is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        MyToken token = new MyToken();
        console.log("MyToken deployed to:", address(token));

        vm.stopBroadcast();
    }
}

Deploy to Tempo Testnet

Deploy using the script:
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url tempo-testnet \
  --broadcast \
  --verify
Deploy without verification:
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url tempo-testnet \
  --broadcast

Verify Contract

Verify on Tempo Explorer:
forge verify-contract \
  --chain-id 42431 \
  --compiler-version v0.8.13+commit.abaa5c0e \
  <CONTRACT_ADDRESS> \
  src/MyToken.sol:MyToken \
  --etherscan-api-key $TEMPO_EXPLORER_KEY

Interacting with TIP-20 Tokens

Use TIP-20 in Contracts

Import the TIP-20 interface:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

interface ITIP20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferWithMemo(address to, uint256 amount, bytes32 memo) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract PaymentProcessor {
    ITIP20 public immutable token;

    constructor(address _token) {
        token = ITIP20(_token);
    }

    function processPayment(address recipient, uint256 amount, bytes32 invoiceId) external {
        require(token.transferWithMemo(recipient, amount, invoiceId), "Transfer failed");
    }
}

Test with TIP-20

Test against forked state:
contract PaymentProcessorTest is Test {
    PaymentProcessor public processor;
    ITIP20 public token;
    
    address constant ALPHA_USD = 0x20c0000000000000000000000000000000000001;

    function setUp() public {
        // Fork Tempo testnet
        vm.createSelectFork("https://rpc.moderato.tempo.xyz");
        
        token = ITIP20(ALPHA_USD);
        processor = new PaymentProcessor(ALPHA_USD);
        
        // Fund the processor with test tokens
        vm.deal(address(processor), 100 ether);
    }

    function testProcessPayment() public {
        address recipient = address(0x123);
        uint256 amount = 100_000_000; // 100 tokens (6 decimals)
        bytes32 invoiceId = bytes32("INV-001");
        
        processor.processPayment(recipient, amount, invoiceId);
        
        assertEq(token.balanceOf(recipient), amount);
    }
}

Local Development

Run Local Tempo Node

Start a local Tempo node:
just localnet
Connect Foundry to localnet:
cast block-number --rpc-url http://localhost:8545

Use Anvil

Start an Anvil instance (Foundry’s local node):
anvil --chain-id 42431
Deploy to Anvil:
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url http://localhost:8545 \
  --broadcast

Advanced Features

Gas Reporting

Enable gas reports in tests:
forge test --gas-report
Add to foundry.toml:
[profile.default]
gas_reports = ["*"]

Fuzzing

Foundry automatically fuzzes test inputs:
function testTransferFuzz(address to, uint256 amount) public {
    vm.assume(to != address(0));
    vm.assume(amount <= 1e9 * 1e6); // Max supply
    
    token.mint(alice, amount);
    vm.prank(alice);
    token.transfer(to, amount);
    
    assertEq(token.balances(to), amount);
}

Invariant Testing

Test contract invariants:
contract TokenInvariantTest is Test {
    MyToken public token;
    
    function setUp() public {
        token = new MyToken();
    }
    
    function invariant_totalSupplyNeverDecreases() public {
        // Total supply can only increase
    }
}

Coverage

Generate test coverage:
forge coverage
Generate detailed report:
forge coverage --report lcov
genhtml lcov.info -o coverage

Debugging

Console Logging

Use console logs in contracts:
import "forge-std/console.sol";

contract MyToken {
    function transfer(address to, uint256 amount) external {
        console.log("Transferring", amount, "to", to);
        // ...
    }
}

Debug Traces

Run tests with debug traces:
forge test --debug testTransfer

Check Transaction

Inspect a transaction:
cast run <TX_HASH> --rpc-url tempo-testnet

Best Practices

  1. Test thoroughly: Use fuzz testing and invariant testing
  2. Fork for integration tests: Test against live Tempo state
  3. Use .env for secrets: Never commit private keys
  4. Verify contracts: Always verify on Tempo Explorer
  5. Gas optimization: Use forge snapshot to track gas usage

Common Commands

CommandDescription
forge buildCompile contracts
forge testRun tests
forge scriptExecute deployment script
forge createDeploy single contract
forge verify-contractVerify on block explorer
cast sendSend transaction
cast callRead contract state
cast rpcCall custom RPC method
anvilStart local node

Next Steps

Foundry Book

Complete Foundry documentation

Solidity Examples

View contract examples

TIP-20 Standard

Learn about Tempo tokens

Deploy Guide

Deploy your first contract