> ## 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

# 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](/sdk/concepts/authentication).

## Interface Definition

```kotlin theme={null}
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

    /** Wait until a transaction is mined and return its receipt */
    suspend fun waitForTransactionReceipt(txHash: String): TransactionReceipt
}
```

## 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)
* **Transaction receipts**: Mined receipt status and logs from `eth_getTransactionReceipt`
* **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.
The SDK calls `waitForTransactionReceipt` after `sendTransaction` to confirm on-chain inclusion and read emitted events.

## WalletConnect Integration

Example using WalletConnect Kotlin SDK:

```kotlin theme={null}
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 suspend fun getAddress(): String = account

    override suspend fun getChainId(): ULong {
        // Parse from account (eip155:1:0x...)
        val parts = account.split(":")
        return parts.getOrNull(1)?.toULongOrNull() ?: 1UL
    }

    override suspend 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 suspend 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 suspend 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")
    }

    override suspend fun waitForTransactionReceipt(txHash: String): TransactionReceipt {
        // Poll eth_getTransactionReceipt via your RPC client until it returns a receipt.
        return pollReceipt(txHash)
    }
}

class WalletException(message: String) : Exception(message)
```

## Web3j Integration

Example using Web3j with a local keystore:

```kotlin theme={null}
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 suspend fun getAddress(): String = credentials.address

    override suspend fun getChainId(): ULong {
        return web3j.ethChainId().send().chainId.toLong().toULong()
    }

    override suspend 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 suspend 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 suspend 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
    }

    override suspend fun waitForTransactionReceipt(txHash: String): TransactionReceipt {
        // Poll eth_getTransactionReceipt via Web3j until it returns a receipt.
        return pollReceipt(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:

```kotlin theme={null}
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 suspend fun getAddress(): String = mockAddress

    override suspend fun getChainId(): ULong = mockChainId

    override suspend fun signMessage(message: String): String {
        return signMessageHandler?.invoke(message)
            ?: "0x" + "ab".repeat(65)
    }

    override suspend fun signTypedData(typedDataJson: String): String {
        return signTypedDataHandler?.invoke(typedDataJson)
            ?: "0x" + "cd".repeat(65)
    }

    override suspend fun sendTransaction(toAddress: String, value: String, data: String): String {
        return sendTransactionHandler?.invoke(toAddress, value, data)
            ?: "0x" + "ef".repeat(32)
    }

    override suspend fun waitForTransactionReceipt(txHash: String): TransactionReceipt {
        return TransactionReceipt(txHash = txHash, status = true, logs = emptyList())
    }
}
```

## Error Handling

Throw appropriate exceptions from delegate methods:

```kotlin theme={null}
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

1. **Validate inputs** - Check addresses and data formats before processing
2. **Handle timeouts** - Set reasonable timeouts for wallet operations
3. **User feedback** - Show loading indicators during signing/transactions
4. **Error messages** - Provide clear error messages for user-facing errors
5. **Thread safety** - Delegate methods are called from background threads

## Next Steps

* [Session Storage](./session-storage) - Secure session storage
