Solidity Integration
Interact with Vara.eth programs from Ethereum smart contracts.
Solidity Integration
Existing Ethereum contracts can call Vara.eth programs through the Mirror contract interface. This enables hybrid architectures where Solidity handles on-chain DeFi composability while Vara.eth handles heavy computation.
Mirror Interface
Every Vara.eth program has a Mirror contract on Ethereum. Your Solidity contracts interact with this Mirror using the IMirror interface:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
interface IMirror {
// Send a message to the Vara.eth program
function sendMessage(
bytes calldata payload,
bool callReply
) external payable returns (bytes32);
// Top up program's executable balance
function executableBalanceTopUp(uint128 value) external;
// Claim value sent back by the program
function claimValue(bytes32 claimedId) external;
// Events emitted by the Mirror
event MessageQueueingRequested(
bytes32 id,
address source,
bytes payload,
uint128 value,
bool callReply
);
event Reply(bytes payload, uint128 value, bytes32 replyTo, bytes4 replyCode);
event StateChanged(bytes32 stateHash);
event Message(bytes32 id, address destination, bytes payload, uint128 value);
event ValueClaimed(bytes32 claimedId, uint128 value);
}See Contract Addresses for the full Router and Mirror interfaces.
Sending Messages from Solidity
Basic Call
contract VaraClient {
IMirror public mirror;
constructor(address _mirror) {
mirror = IMirror(_mirror);
}
function callProgram(bytes calldata payload) external {
// Fire-and-forget: message queued, executed by validators
mirror.sendMessage(payload, false);
}
}With Value Transfer
To top up Executable Balance in wVARA, approve the program (Mirror address) first, then call executableBalanceTopUp:
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract VaraDepositor {
IMirror public mirror;
IERC20 public wvara;
constructor(address _mirror, address _wvara) {
mirror = IMirror(_mirror);
wvara = IERC20(_wvara);
}
function topUpExecutableBalance(uint128 amount) external {
// Approve the program (Mirror address) as spender
wvara.approve(address(mirror), amount);
mirror.executableBalanceTopUp(amount);
}
}To send ETH value with a message, use payable call value:
function sendWithEth(bytes calldata payload) external payable {
mirror.sendMessage{value: msg.value}(payload, false);
}Requesting a Reply
Set callReply = true to receive the program's reply as an on-chain event:
function callWithReply(bytes calldata payload) external {
// Reply will be emitted as a Reply event on the Mirror
mirror.sendMessage(payload, true);
}Asynchronous Replies
Replies are emitted as events, not as synchronous return values. The execution happens asynchronously — your contract sends a message, and the reply arrives in a later transaction (the batch commitment).
Encoding Payloads
Vara.eth programs use SCALE encoding by default. When a program is deployed with ABI (recommended), the Mirror accepts standard ABI-encoded payloads.
With ABI (Recommended)
// Encode a call to: TokenService::Transfer(to, amount)
bytes memory payload = abi.encodeWithSignature(
"Transfer(address,uint256)",
recipient,
amount
);
mirror.sendMessage(payload, false);Using Generated ABI
Generate a Solidity interface from the program's IDL:
cargo sails sol --idl-path ./target/wasm32-gear/release/my_program.idlThis generates a typed Solidity interface:
import {IMyProgram} from "./IMyProgram.sol";
contract Client {
IMyProgram public program;
function doSomething() external {
// Type-safe call via generated interface
program.myService_myMethod(arg1, arg2);
}
}Listening for Events
Reply Events
contract ReplyListener {
IMirror public mirror;
// Called by off-chain service when Reply event is detected
function handleReply(
bytes calldata payload,
uint128 value,
bytes32 replyTo,
bytes4 replyCode
) external {
// replyCode == 0x00000000 means success
require(replyCode == bytes4(0), "Program returned error");
// Decode and use payload...
}
}State Change Events
// Off-chain listener (ethers.js)
mirror.on("StateChanged", (stateHash) => {
console.log(`Program state updated: ${stateHash}`);
});Custom Program Events
Programs can define custom events that are re-emitted as native EVM logs by the Mirror:
mirror.on("Transferred", (from, to, amount) => {
console.log(`Transfer: ${from} -> ${to}: ${amount}`);
});Claiming Value
When a Vara.eth program sends value to an address, the recipient claims it through the Mirror:
function claimMyValue(bytes32 claimedId) external {
// claimedId comes from the Message event emitted by Mirror
mirror.claimValue(claimedId);
// Claimed value is transferred to msg.sender
}Architecture Patterns
Computation Offloading
Keep DeFi composability in Solidity, offload heavy logic to Vara.eth:
User -> Solidity Router -> Mirror.sendMessage() -> Vara.eth Program
|
Heavy computation
|
Reply event <- Mirror
|
Off-chain service reads reply
|
Triggers next Solidity actionHybrid DeFi
contract HybridDEX {
IMirror public pricingEngine; // Vara.eth handles complex pricing
IUniswap public uniswap; // Solidity handles token swaps
function executeSwap(
address tokenIn,
address tokenOut,
uint256 amountIn
) external {
// 1. Ask Vara.eth program for optimal price/route
bytes memory query = abi.encode(tokenIn, tokenOut, amountIn);
pricingEngine.sendMessage(query, true);
// 2. Reply arrives asynchronously with routing data
// 3. Off-chain keeper executes the swap using the route
}
}Oracle/Aggregator
contract VaraOracle {
IMirror public oracle;
mapping(bytes32 => uint256) public prices;
function updatePrice(
bytes32 asset,
uint256 price,
bytes calldata proof
) external {
// Verify proof from Vara.eth execution
prices[asset] = price;
}
}Important Considerations
- Asynchronous execution: Messages sent via
sendMessageare not executed immediately. They enter the program's message queue and are processed by executors in the next batch. - Gas costs: Sending a message via Mirror costs standard Ethereum gas (~60-100k gas). The program's execution cost is paid from its Executable Balance, not by the caller.
- No synchronous returns: Unlike typical Solidity function calls, you cannot get a return value from
sendMessage. UsecallReply = trueand listen forReplyevents, or read state afterStateChanged. - ABI vs SCALE: Deploy programs with ABI for the smoothest Solidity integration. Without ABI, you'll need to SCALE-encode payloads manually.
- Value handling:
sendMessagecarries ETH viamsg.value. wVARA is used to fund executable balance viaexecutableBalanceTopUp, with prior ERC-20 approve to the program (Mirror) address.