Documentation Index
Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt
Use this file to discover all available pages before exploring further.
Wallet Integration
The WalletDelegate interface is the bridge between the Privacy Boost SDK and your wallet. This guide covers implementing it for various wallet types, expanding on the brief examples in Authentication.
Interface Definition
interface WalletDelegate {
/** Get the connected wallet address */
suspend fun getAddress(): String
/** Get the current chain ID */
suspend fun getChainId(): ULong
/** Sign a personal message (EIP-191) */
suspend fun signMessage(message: String): String
/** Sign EIP-712 typed data */
suspend fun signTypedData(typedDataJson: String): String
/** Send a transaction */
suspend fun sendTransaction(toAddress: String, value: String, data: String): String
}
Implementation Requirements
Return Values
- Addresses: Checksummed hex strings with
0x prefix (42 characters)
- Signatures: Hex strings with
0x prefix (132 characters for 65 bytes)
- Transaction hashes: Hex strings with
0x prefix (66 characters)
- Chain ID: ULong matching expected network
Suspend Behavior
All WalletDelegate methods are suspend functions. The SDK calls them within a coroutine context, so your implementation can use Kotlin coroutines natively.
WalletConnect Integration
Example using WalletConnect Kotlin SDK:
import com.walletconnect.web3.wallet.client.Web3Wallet
import kotlinx.coroutines.runBlocking
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class WalletConnectDelegate(
private val sessionTopic: String,
private val account: String
) : WalletDelegate {
override fun getAddress(): String = account
override fun getChainId(): ULong {
// Parse from account (eip155:1:0x...)
val parts = account.split(":")
return parts.getOrNull(1)?.toULongOrNull() ?: 1UL
}
override fun signMessage(message: String): String {
val latch = CountDownLatch(1)
var result: String? = null
var error: Throwable? = null
val request = Web3Wallet.Request.PersonalSign(
sessionTopic = sessionTopic,
message = message,
address = getAddress()
)
Web3Wallet.request(request,
onSuccess = { signature ->
result = signature
latch.countDown()
},
onError = { e ->
error = e
latch.countDown()
}
)
if (!latch.await(60, TimeUnit.SECONDS)) {
throw WalletException("Signing timeout")
}
error?.let { throw it }
return result ?: throw WalletException("No signature returned")
}
override fun signTypedData(typedDataJson: String): String {
val latch = CountDownLatch(1)
var result: String? = null
var error: Throwable? = null
val request = Web3Wallet.Request.SignTypedData(
sessionTopic = sessionTopic,
data = typedDataJson,
address = getAddress()
)
Web3Wallet.request(request,
onSuccess = { signature ->
result = signature
latch.countDown()
},
onError = { e ->
error = e
latch.countDown()
}
)
if (!latch.await(60, TimeUnit.SECONDS)) {
throw WalletException("Signing timeout")
}
error?.let { throw it }
return result ?: throw WalletException("No signature returned")
}
override fun sendTransaction(toAddress: String, value: String, data: String): String {
val latch = CountDownLatch(1)
var result: String? = null
var error: Throwable? = null
val request = Web3Wallet.Request.SendTransaction(
sessionTopic = sessionTopic,
from = getAddress(),
to = toAddress,
value = value,
data = data
)
Web3Wallet.request(request,
onSuccess = { txHash ->
result = txHash
latch.countDown()
},
onError = { e ->
error = e
latch.countDown()
}
)
if (!latch.await(120, TimeUnit.SECONDS)) {
throw WalletException("Transaction timeout")
}
error?.let { throw it }
return result ?: throw WalletException("No transaction hash returned")
}
}
class WalletException(message: String) : Exception(message)
Web3j Integration
Example using Web3j with a local keystore:
import org.web3j.crypto.Credentials
import org.web3j.crypto.Sign
import org.web3j.protocol.Web3j
import org.web3j.protocol.core.DefaultBlockParameterName
import org.web3j.protocol.http.HttpService
import org.web3j.tx.RawTransactionManager
import org.web3j.utils.Numeric
class Web3jDelegate(
private val credentials: Credentials,
private val web3j: Web3j
) : WalletDelegate {
override fun getAddress(): String = credentials.address
override fun getChainId(): ULong {
return web3j.ethChainId().send().chainId.toLong().toULong()
}
override fun signMessage(message: String): String {
val messageBytes = message.toByteArray()
val prefix = "\u0019Ethereum Signed Message:\n${messageBytes.size}"
val prefixedMessage = (prefix + message).toByteArray()
val signature = Sign.signPrefixedMessage(prefixedMessage, credentials.ecKeyPair)
val r = Numeric.toHexStringNoPrefix(signature.r)
val s = Numeric.toHexStringNoPrefix(signature.s)
val v = Numeric.toHexStringNoPrefix(byteArrayOf(signature.v[0]))
return "0x$r$s$v"
}
override fun signTypedData(typedDataJson: String): String {
// Parse and sign EIP-712 typed data
val typedData = parseTypedData(typedDataJson)
val structHash = hashTypedData(typedData)
val signature = Sign.signMessage(structHash, credentials.ecKeyPair, false)
val r = Numeric.toHexStringNoPrefix(signature.r)
val s = Numeric.toHexStringNoPrefix(signature.s)
val v = Numeric.toHexStringNoPrefix(byteArrayOf(signature.v[0]))
return "0x$r$s$v"
}
override fun sendTransaction(toAddress: String, value: String, data: String): String {
val nonce = web3j.ethGetTransactionCount(
credentials.address,
DefaultBlockParameterName.LATEST
).send().transactionCount
val gasPrice = web3j.ethGasPrice().send().gasPrice
val gasLimit = estimateGas(toAddress, value, data)
val txManager = RawTransactionManager(web3j, credentials, getChainId().toLong())
val txHash = txManager.sendTransaction(
gasPrice,
gasLimit,
toAddress,
data,
value.toBigInteger()
).transactionHash
return txHash
}
private fun estimateGas(to: String, value: String, data: String): java.math.BigInteger {
val estimate = web3j.ethEstimateGas(
org.web3j.protocol.core.methods.request.Transaction.createFunctionCallTransaction(
credentials.address,
null,
null,
null,
to,
value.toBigInteger(),
data
)
).send()
return estimate.amountUsed.multiply(java.math.BigInteger.valueOf(120)).divide(java.math.BigInteger.valueOf(100))
}
private fun parseTypedData(json: String): TypedData {
// Implementation depends on your JSON parsing library
TODO("Parse EIP-712 typed data")
}
private fun hashTypedData(typedData: TypedData): ByteArray {
// Implementation of EIP-712 hashing
TODO("Hash typed data")
}
}
Testing with Mock Wallet
For testing, create a mock delegate:
class MockWalletDelegate(
private val mockAddress: String = "0x1234567890123456789012345678901234567890",
private val mockChainId: ULong = 1UL
) : WalletDelegate {
var signMessageHandler: ((String) -> String)? = null
var signTypedDataHandler: ((String) -> String)? = null
var sendTransactionHandler: ((String, String, String) -> String)? = null
override fun getAddress(): String = mockAddress
override fun getChainId(): ULong = mockChainId
override fun signMessage(message: String): String {
return signMessageHandler?.invoke(message)
?: "0x" + "ab".repeat(65)
}
override fun signTypedData(typedDataJson: String): String {
return signTypedDataHandler?.invoke(typedDataJson)
?: "0x" + "cd".repeat(65)
}
override fun sendTransaction(toAddress: String, value: String, data: String): String {
return sendTransactionHandler?.invoke(toAddress, value, data)
?: "0x" + "ef".repeat(32)
}
}
Error Handling
Throw appropriate exceptions from delegate methods:
sealed class WalletDelegateException(message: String) : Exception(message) {
class NotConnected : WalletDelegateException("Wallet not connected")
class UserRejected : WalletDelegateException("User rejected the request")
class NetworkError(message: String) : WalletDelegateException("Network error: $message")
class InvalidSignature : WalletDelegateException("Invalid signature returned")
class TransactionFailed(message: String) : WalletDelegateException("Transaction failed: $message")
class Timeout : WalletDelegateException("Operation timed out")
}
Best Practices
- Validate inputs - Check addresses and data formats before processing
- Handle timeouts - Set reasonable timeouts for wallet operations
- User feedback - Show loading indicators during signing/transactions
- Error messages - Provide clear error messages for user-facing errors
- Thread safety - Delegate methods are called from background threads
Next Steps