Interfaces let your Stylus contract call other contracts (Solidity, Stylus/Rust, etc.) using their Solidity ABI. You declare them with sol_interface!, then invoke methods from Rust with a VM handle (self.vm()) and a call context (Call).
Use sol_interface! with Solidity syntax (keep CamelCase names; Stylus computes selectors from these exact names):
1sol_interface! {
2 interface IService {
3 function makePayment(address user) payable external returns (string);
4 function getConstant() pure external returns (bytes32);
5 }
6
7 interface ITree {
8 // other interface methods
9 }
10}1sol_interface! {
2 interface IService {
3 function makePayment(address user) payable external returns (string);
4 function getConstant() pure external returns (bytes32);
5 }
6
7 interface ITree {
8 // other interface methods
9 }
10}This generates Rust types IService and ITree that you can construct from an address (e.g., IService::new(addr)) or accept as parameters.
Note on naming: Solidity
makePayment(CamelCase) becomesmake_paymentwhen called from Rust. The ABI selector uses the Solidity name; the Rust method is snake_case for ergonomics.
Create an interface bound to a target address, build the right Call context, and pass self.vm():
1use stylus_sdk::{
2 alloy_primitives::Address,
3 call::Call,
4 prelude::*,
5};
6
7sol_interface! {
8 interface IService {
9 function makePayment(address user) payable external returns (string);
10 }
11}
12
13#[public]
14impl MyContract {
15 #[payable]
16 pub fn pay_user(&mut self, service_addr: Address, user: Address) -> Result<String, Vec<u8>> {
17 let service = IService::new(service_addr);
18 let ctx = Call::new_payable(self, self.vm().msg_value()); // set value; add .gas(...) if needed
19 Ok(service.make_payment(self.vm(), ctx, user)?)
20 }
21}1use stylus_sdk::{
2 alloy_primitives::Address,
3 call::Call,
4 prelude::*,
5};
6
7sol_interface! {
8 interface IService {
9 function makePayment(address user) payable external returns (string);
10 }
11}
12
13#[public]
14impl MyContract {
15 #[payable]
16 pub fn pay_user(&mut self, service_addr: Address, user: Address) -> Result<String, Vec<u8>> {
17 let service = IService::new(service_addr);
18 let ctx = Call::new_payable(self, self.vm().msg_value()); // set value; add .gas(...) if needed
19 Ok(service.make_payment(self.vm(), ctx, user)?)
20 }
21}Call::new()Call::new_mutating(self)Call::new_payable(self, value) (optionally .gas(limit))If your function receives the interface value directly, just build the context and call:
1use stylus_sdk::{call::Call, prelude::*};
2use stylus_sdk::alloy_primitives::Address;
3
4sol_interface! {
5 interface IService {
6 function makePayment(address user) payable external returns (string);
7 }
8}
9
10#[public]
11impl MyContract {
12 #[payable]
13 pub fn do_call(&mut self, account: IService, user: Address) -> Result<String, Vec<u8>> {
14 let ctx = Call::new_payable(self, self.vm().msg_value());
15 Ok(account.make_payment(self.vm(), ctx, user)?)
16 }
17}1use stylus_sdk::{call::Call, prelude::*};
2use stylus_sdk::alloy_primitives::Address;
3
4sol_interface! {
5 interface IService {
6 function makePayment(address user) payable external returns (string);
7 }
8}
9
10#[public]
11impl MyContract {
12 #[payable]
13 pub fn do_call(&mut self, account: IService, user: Address) -> Result<String, Vec<u8>> {
14 let ctx = Call::new_payable(self, self.vm().msg_value());
15 Ok(account.make_payment(self.vm(), ctx, user)?)
16 }
17}For a dedicated “methods” interface, use a non-mutating context for view/pure and a mutating context for write:
1use stylus_sdk::{call::Call, prelude::*};
2
3sol_interface! {
4 interface IMethods {
5 function viewFoo() external view;
6 function writeFoo() external;
7 }
8}
9
10#[public]
11impl Contract {
12 pub fn call_view(&self, addr: Address) -> Result<(), Vec<u8>> {
13 let ext = IMethods::new(addr);
14 Ok(ext.view_foo(self.vm(), Call::new())?)
15 }
16
17 pub fn call_write(&mut self, addr: Address) -> Result<(), Vec<u8>> {
18 let ext = IMethods::new(addr);
19 let ctx = Call::new_mutating(self);
20 Ok(ext.write_foo(self.vm(), ctx)?)
21 }
22}1use stylus_sdk::{call::Call, prelude::*};
2
3sol_interface! {
4 interface IMethods {
5 function viewFoo() external view;
6 function writeFoo() external;
7 }
8}
9
10#[public]
11impl Contract {
12 pub fn call_view(&self, addr: Address) -> Result<(), Vec<u8>> {
13 let ext = IMethods::new(addr);
14 Ok(ext.view_foo(self.vm(), Call::new())?)
15 }
16
17 pub fn call_write(&mut self, addr: Address) -> Result<(), Vec<u8>> {
18 let ext = IMethods::new(addr);
19 let ctx = Call::new_mutating(self);
20 Ok(ext.write_foo(self.vm(), ctx)?)
21 }
22}Call::new_payable(self, value).gas(limit)Call::new()Call::new_mutating(self)Example:
1let ctx = Call::new_payable(self, self.vm().msg_value())
2 .gas(self.vm().evm_gas_left() / 2);1let ctx = Call::new_payable(self, self.vm().msg_value())
2 .gas(self.vm().evm_gas_left() / 2);