Custom Signers
The Interface
To implement a custom signer, you must implement the Signer interface.
public interface Signer {
Signature signTransaction(UnsignedTransaction tx, long chainId);
Signature signMessage(byte[] message);
Address address();
}Example: External Service Integration
Here is how you might implement a signer that delegates to an external API (e.g., Privy or a backend KMS).
import sh.brane.core.crypto.Signer;
import sh.brane.core.crypto.Signature;
import sh.brane.core.types.Address;
import sh.brane.core.tx.UnsignedTransaction;
import sh.brane.core.crypto.Keccak256;
// Dummy interface for the example
interface ExternalService {
Signature sign(String keyId, byte[] hash);
}
public class RemoteSigner implements Signer {
private final String keyId;
private final Address address;
private final ExternalService service;
public RemoteSigner(String keyId, Address address, ExternalService service) {
this.keyId = keyId;
this.address = address;
this.service = service;
}
@Override
public Address address() {
return address;
}
@Override
public Signature signTransaction(UnsignedTransaction tx, long chainId) {
// 1. Get the transaction hash (preimage)
byte[] preimage = tx.encodeForSigning(chainId);
byte[] hash = Keccak256.hash(preimage);
// 2. Request signature from external service
// (This is where you call Privy, AWS KMS, etc.)
return service.sign(this.keyId, hash);
}
@Override
public Signature signMessage(byte[] message) {
// 1. Create the EIP-191 prefixed message
byte[] prefix = ("\u0019Ethereum Signed Message:\n" + message.length)
.getBytes(java.nio.charset.StandardCharsets.UTF_8);
byte[] prefixedMessage = java.nio.ByteBuffer.allocate(prefix.length + message.length)
.put(prefix)
.put(message)
.array();
// 2. Hash the prefixed message
byte[] hash = Keccak256.hash(prefixedMessage);
// 3. Request signature for the hash
Signature sig = service.sign(this.keyId, hash);
// Adjust v to be 27 or 28 for EIP-191 compatibility
return new Signature(sig.r(), sig.s(), sig.v() + 27);
}
}Usage with Brane.Signer
Once you have your custom signer, pass it to Brane.connect() to create a Brane.Signer client.
import sh.brane.rpc.Brane;
// Assume service is defined
ExternalService service = ...;
Signer mySigner = new RemoteSigner(
"key-123",
new Address("0x..."),
service
);
// Create a Brane.Signer client with your custom signer
Brane.Signer client = Brane.connect("https://rpc.sepolia.org", mySigner);
// Now you can send transactions
TransactionReceipt receipt = client.sendTransactionAndWait(request, 60_000, 1_000);