Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Type-Safe Bindings

1. Define Interface

Create a Java interface that mirrors your Solidity contract.

import sh.brane.core.types.Address;
import sh.brane.core.model.TransactionReceipt;
import java.math.BigInteger;
 
public interface Erc20 {
    // View function
    BigInteger balanceOf(Address account);
 
    // State-changing function
    TransactionReceipt transfer(Address to, BigInteger amount);
}

2. Bind Contract

Use BraneContract.bind to create an instance of your interface.

import sh.brane.contract.BraneContract;
 
// signerClient is a Brane.Signer (which also has Reader methods)
Erc20 token = BraneContract.bind(
    new Address("0x..."),
    abiJson,
    signerClient,
    Erc20.class
);

3. Interact

Call methods directly on the interface.

// Read
BigInteger balance = token.balanceOf(myAddress);
 
// Write
TransactionReceipt receipt = token.transfer(recipient, amount);

Validation

When you call BraneContract.bind, the SDK performs strict validation to ensure your Java interface matches the contract ABI. This "fail-fast" behavior prevents runtime errors.

The validation checks:

  1. Method Existence: Every method in your interface must exist in the ABI.
  2. Parameter Count: The number of arguments must match.
  3. Type Compatibility: Java types must be compatible with Solidity types (e.g., uint256 -> BigInteger).
  4. Mutability: You cannot bind a void return type to a view function (it must return a value).

If any check fails, bind throws an IllegalArgumentException with a detailed error message explaining the mismatch.

Supported Types

The binding system automatically maps Solidity types to Java types:

SolidityJava
uint256, int256java.math.BigInteger
addresssh.brane.core.types.Address
booljava.lang.Boolean
stringjava.lang.String
bytessh.brane.core.types.HexData
uint8...uint64java.math.BigInteger
T[] (arrays)java.util.List<T> or T[]

Payable Functions

To send ETH with a contract call, use the @Payable annotation. The first parameter must be Wei, which specifies the amount to send. This parameter is not passed to the contract function.

import sh.brane.contract.Payable;
import sh.brane.core.types.Wei;
import sh.brane.core.types.Address;
import sh.brane.core.model.TransactionReceipt;
 
public interface WethContract {
    // deposit() is payable in Solidity - sends ETH to wrap
    @Payable
    TransactionReceipt deposit(Wei value);
 
    // mint(address to) is payable - sends ETH and mints to recipient
    @Payable
    TransactionReceipt mint(Wei value, Address to);
}
Usage:
WethContract weth = BraneContract.bind(address, abiJson, signerClient, WethContract.class);
 
// Wrap 1 ETH
weth.deposit(Wei.fromEther(new BigDecimal("1.0")));
 
// Mint with 0.5 ETH
weth.mint(Wei.fromEther(new BigDecimal("0.5")), recipientAddress);

Contract Options

Customize transaction behavior with ContractOptions:

import sh.brane.contract.ContractOptions;
import sh.brane.core.types.Wei;
import java.time.Duration;
 
ContractOptions options = ContractOptions.builder()
    .gasLimit(500_000L)                              // Gas limit for transactions
    .timeout(Duration.ofSeconds(30))                 // Wait timeout for receipts
    .pollInterval(Duration.ofMillis(500))            // Polling interval
    .transactionType(ContractOptions.TransactionType.EIP1559)  // EIP-1559 (default)
    .maxPriorityFee(Wei.gwei(2))                     // Priority fee (tip)
    .build();
 
Erc20 token = BraneContract.bind(
    address,
    abiJson,
    signerClient,
    Erc20.class,
    options  // Pass custom options
);

Available Options

OptionDefaultDescription
gasLimit300,000Maximum gas for transactions
timeout10 secondsMax wait time for transaction confirmation
pollInterval500msInterval between receipt checks
transactionTypeEIP1559Transaction type (EIP1559 or LEGACY)
maxPriorityFee2 gweiPriority fee for EIP-1559 transactions

Limitations

Tuple/Struct Returns

Functions that return Solidity structs or tuples are not supported by the proxy binding system. The following will fail at bind time:

// Solidity
struct Position {
    uint256 x;
    uint256 y;
}
 
function getPosition() external view returns (Position memory);
// Java - This will NOT work with BraneContract.bind()
public record Position(BigInteger x, BigInteger y) {}
 
public interface MyContract {
    Position getPosition();  // Throws IllegalArgumentException at bind time
}

Workaround: Use the low-level RPC client to call the function and manually decode the response:

// Encode the function call manually
HexData calldata = Abi.encodeCall(abi, "getPosition");
 
// Make a raw eth_call
String rawHex = client.call(Map.of(
    "to", address.value(),
    "data", calldata.value()
), "latest");
 
// Decode the response using Abi.decode()...