Skip to main content
Sigil contracts can call other contracts with full type safety. The framework provides two macros for importing contract interfaces: interface! for dynamic addresses and import! for fixed addresses.

The interface! Macro

Import another contract’s interface without specifying its address:
What it does:
  • Reads the WIT file from the specified path
  • Generates type-safe Rust bindings
  • Creates functions you can call from your contract

The import! Macro

Import a specific contract instance at a known address:
Parameters:
  • name - Contract name
  • mod_name - Rust module name (avoid conflicts)
  • height - Block height where contract was deployed
  • tx_index - Transaction index in that block
  • path - Path to WIT file
The macro generates functions similar to interface! but with the contract address fixed at compile time.

When to Use Each

Use interface! for dynamic addresses (passed as parameters):
Use import! for fixed addresses (known at compile time):

Standard Interfaces

The interface! macro enables standards similar to Ethereum’s ERC-20: define an interface once, and contracts can work with any implementation of that interface.

Defining a Token Standard

Extract common token functions into a standalone WIT file that any token can implement:

Using Standards with Dynamic Addresses

Contracts accept any contract address that implements the standard interface:
The AMM example demonstrates this pattern, accepting any token address and calling its transfer functions.

How This Differs from Ethereum

Ethereum’s ERC-20: A social convention where contracts implement functions named transfer, balanceOf, approve, etc. No compile-time or runtime verification that a contract actually implements the standard—you just call functions and hope they exist. Sigil’s interface!: The WIT interface provides strong typing in your contract code:
  • The compiler ensures your contract makes valid calls to the interface
  • The compiler ensures implementations export all required functions with correct signatures
  • The runtime validates that the target contract matches the interface before executing calls
  • Interface mismatches surface as clear runtime errors, not undefined behavior

Making Cross-Contract Calls

Basic Example

From the shared-account contract:

ContractAddress Type

Creating addresses:

Call Context and Signers

The Signer Parameter

When making cross-contract calls, you choose whose authority to use: Execute as the current user:
Execute as the contract itself:
When to use contract_signer():
  • Contract holds tokens on behalf of users
  • Contract acts as custodian
  • Escrow patterns

Error Propagation

Errors from cross-contract calls propagate automatically with ?:
Cross-contract errors roll back all storage changes in the entire call chain.

Re-entrancy Protection

The runtime prevents re-entrancy automatically:
Example:
Why this matters:
  • Prevents common exploit patterns
  • Simplifies reasoning about contract behavior
  • No need for re-entrancy guards

Multi-Contract Interaction Example

AMM (Automated Market Maker) calling Token contract:
Pattern:
  1. Call external contracts first (get tokens)
  2. Validate received amounts
  3. Update local state
  4. Return results

The foreign::call Low-Level API

For advanced use cases, you can make dynamic calls:
Signature:
When to use:
  • Proxy contracts
  • Generic delegation patterns
  • When you need to construct calls dynamically
  • Fallback handlers
Most contracts should use interface! or import! instead for type safety.

Best Practices

1. Use interface! for flexibility
2. Validate before external calls (CEI pattern)
3. Handle cross-contract errors
4. Document contract dependencies