⚠ This example is being rewritten against the verified V0.8 syntax. The code shown may use a pre-V0.8 sketch that will not compile as-is. For the canonical, compiler-verified V0.8 examples — every one of them derived from a compiler test fixture — see the Playground Examples gallery. The 4 reference examples already updated on this site: Hello, ERC-20 Token, FHE Basics, Cryptographic Amnesia. The remaining ones land in Sprint 29.

05 — External Calls

Calling an interface

Define an interface and call through it:

interface IERC20 {
  action transfer(to: Address, amount: u256) -> Bool
  action balanceOf(who: Address) -> u256
}

contract Spender {
  field token: Address

  action spend(amount: u256) {
    let tok = IERC20(self.token);
    let ok = tok.transfer(msg.sender, amount);
    require(ok, TransferFailed);
  }
}

Safety: reentrancy guard

Covenant’s built-in @nonreentrant decorator locks the contract for the duration of the action:

@nonreentrant
action withdraw(amount: u256) {
  require(self.balances[msg.sender] >= amount, InsufficientBalance);
  self.balances[msg.sender] -= amount;      // effect before interaction
  let ok = IERC20(self.token).transfer(msg.sender, amount);
  require(ok, TransferFailed);
}

The compiler also enforces checks-effects-interactions order via a static lint — disable with @allow(cei_violation) only when you have verified the call is safe.

Low-level calls

For untyped calls (e.g. proxies):

let result = call(target, data: calldata_bytes, value: 0);
require(result.success, CallFailed);

Sending ETH

action fund(recipient: Address) {
  require(msg.value > 0, ZeroValue);
  transfer_eth(recipient, msg.value);
}

transfer_eth is equivalent to Solidity call{value: amount}("") with a 2300 gas stipend check disabled (Covenant does not use the Solidity 2300-gas anti-pattern).

staticcall

Use @view actions on external interfaces — the compiler emits STATICCALL automatically:

@view
action quote(amount: u256) -> u256 {
  return IOracle(self.oracle).price() * amount / 1e18;
}