Simulating Calls
Brane exposes eth_simulateV1 via Brane.simulate(). This lets you dry-run one or more calls (in sequence), optionally applying state overrides (balance/nonce/code/storage) and collecting richer output such as per-call gas used, logs, and asset changes.
Basic Usage
1. Build a Simulation Request
import sh.brane.rpc.BlockTag;
import sh.brane.rpc.Brane;
import sh.brane.rpc.SimulateCall;
import sh.brane.rpc.SimulateRequest;
import sh.brane.core.types.Address;
import sh.brane.core.types.HexData;
Address from = new Address("0x1111111111111111111111111111111111111111");
Address to = new Address("0x2222222222222222222222222222222222222222");
SimulateRequest request = SimulateRequest.builder()
.account(from) // default "from" if not provided per-call
.call(SimulateCall.builder()
.to(to)
.data(new HexData("0x1234"))
.build())
.blockTag(BlockTag.LATEST)
.traceAssetChanges(true)
.build();2. Execute
import sh.brane.rpc.SimulateResult;
Brane client = Brane.connect("https://eth-mainnet.example.com");
SimulateResult result = client.simulate(request);Handling Results
Each call produces a CallResult in the same order as the input calls.
import sh.brane.rpc.CallResult;
for (var r : result.results()) {
switch (r) {
case CallResult.Success s -> {
System.out.println("ok gasUsed=" + s.gasUsed());
System.out.println("returnData=" + s.returnData());
}
case CallResult.Failure f -> {
System.out.println("fail gasUsed=" + f.gasUsed());
System.out.println("error=" + f.errorMessage());
System.out.println("revertData=" + f.revertData());
}
}
}Asset Changes
If you enabled traceAssetChanges(true), assetChanges() may contain token balance changes observed during the simulation:
var changes = result.assetChanges();
if (changes != null) {
for (var c : changes) {
System.out.println(c.token().symbol() + " diff=" + c.value().diff());
}
}State Overrides
State overrides allow you to simulate "what if" scenarios without touching real chain state.
import sh.brane.rpc.AccountOverride;
import sh.brane.core.types.Wei;
Address who = new Address("0x3333333333333333333333333333333333333333");
SimulateRequest requestWithOverride = SimulateRequest.builder()
.account(from)
.call(SimulateCall.builder().to(to).data(new HexData("0x1234")).build())
.stateOverride(who, AccountOverride.builder()
.balance(Wei.fromEther(new java.math.BigDecimal("1000")))
.build())
.build();Error Handling
import sh.brane.core.error.RpcException;
import sh.brane.rpc.exception.SimulateNotSupportedException;
try {
var result = client.simulate(request);
} catch (SimulateNotSupportedException e) {
// Node/provider does not implement eth_simulateV1
// Consider falling back to eth_call / createAccessList depending on your use case.
System.err.println(e.getMessage());
} catch (RpcException e) {
// Other RPC failures (timeouts, reverts surfaced by the node, etc.)
System.err.println(e.getMessage());
}Technical Notes
- Block selection:
SimulateRequest.blockTag()defaults to"latest"if not set. - Request format: Brane serializes the request payload as the first parameter and passes the block tag as the second parameter, per the
eth_simulateV1API shape. - Spec reference: See the eth_simulateV1 specification and supplementary notes.