# Wallet recipes

> Encrypt user data, message another identity, sign things anyone can verify, and derive per-app keys. Four BRC-100 calls you can use the moment a wallet connects.

> Agents: search these docs with the `search_docs` tool on the MCP server at https://createbsvapp.vercel.app/mcp, or read everything at https://createbsvapp.vercel.app/llms-full.txt.

Login and signed requests are just the start. The connected `wallet` from `useWallet()` is a full [BRC-100](https://createbsvapp.vercel.app/docs/bsv-primer#wallets) wallet, so your app can ask it for cryptography without ever touching a key. Depending on the protocol's security level, the wallet may ask the user to approve the first call.

Every recipe on this page was run against real wallet instances (`ProtoWallet` from `@bsv/sdk`, which implements the same calls) with the SDK version the scaffold installs.

```tsx
const { wallet } = useWallet() // WalletInterface from @bsv/sdk, null until connected
```

## Protocol IDs in 30 seconds

Every call names a **protocol** and a **key ID**. The wallet derives a fresh key from them ([BRC-43](https://createbsvapp.vercel.app/docs/bsv-primer#key-derivation-briefly)), so your app's keys never collide with another app's.

```ts
const protocolID: [1, string] = [1, 'my app notes'] // [security level, name]
const keyID = 'note-1'                             // any string, per item or per purpose
```

| Level | Wallet asks the user | Use for |
| --- | --- | --- |
| `0` | never | low-stakes, high-frequency operations |
| `1` | once per app | most app features |
| `2` | once per app *and* counterparty | messages and signatures involving other identities |

Names use lowercase letters, numbers and spaces. Pick one per feature and never reuse it for something else.

## Encrypt data only the user can read

Store private notes, drafts or settings on your server without being able to read them. Use counterparty `'self'`:

```ts [client/src/notes.ts] twoslash
import { Utils, type WalletInterface } from '@bsv/sdk'

const notes = { protocolID: [1, 'my app notes'] as [1, string], keyID: 'note-1' }

export async function seal (wallet: WalletInterface, text: string): Promise<number[]> {
  const { ciphertext } = await wallet.encrypt({ ...notes, plaintext: Utils.toArray(text, 'utf8'), counterparty: 'self' })
  return ciphertext // store this; it's useless without the user's wallet
}

export async function unseal (wallet: WalletInterface, ciphertext: number[]): Promise<string> {
  const { plaintext } = await wallet.decrypt({ ...notes, ciphertext, counterparty: 'self' })
  return Utils.toUTF8(plaintext)
}
```

::: tip Your database becomes boring, in a good way
A breach leaks ciphertext. Only the user's wallet, with the same protocol and key ID, can decrypt it.
:::

## Send a message only one identity can read

Encrypt *to* another user's identity key. Only their wallet can open it, and it proves it came from you:

```ts twoslash
import { Utils, type WalletInterface } from '@bsv/sdk'
declare const aliceWallet: WalletInterface, bobWallet: WalletInterface
declare const aliceIdentityKey: string, bobIdentityKey: string
// ---cut---
const dm = { protocolID: [2, 'my app messages'] as [2, string], keyID: 'msg-1' }

// Alice, to Bob:
const { ciphertext } = await aliceWallet.encrypt({ ...dm, plaintext: Utils.toArray('hi bob', 'utf8'), counterparty: bobIdentityKey })

// Bob, from Alice:
const { plaintext } = await bobWallet.decrypt({ ...dm, ciphertext, counterparty: aliceIdentityKey })
```

The key is derived from *both* identities, so the pair is what matters. Use the same `protocolID` and `keyID` on both sides, and swap the counterparty.

## Sign something anyone can verify

Public votes, attestations, "I wrote this" stamps. Sign with counterparty `'anyone'`, and verify with an `'anyone'` wallet, which needs no secret at all:

```ts twoslash
import type { WalletInterface } from '@bsv/sdk'
declare const wallet: WalletInterface
declare const signerIdentityKey: string
// ---cut---
import { ProtoWallet, Utils } from '@bsv/sdk'

const votes = { protocolID: [2, 'my app votes'] as [2, string], keyID: 'poll-42' }
const data = Utils.toArray(JSON.stringify({ vote: 'yes', poll: 42 }), 'utf8')

// In the browser, with the user's wallet:
const { signature } = await wallet.createSignature({ ...votes, data, counterparty: 'anyone' })

// Anywhere, later (your server, another app, a script):
const verifier = new ProtoWallet('anyone')
const ok = await verifier
  .verifySignature({ ...votes, data, signature, counterparty: signerIdentityKey })
  .then(() => true, () => false)
```

::: warning `verifySignature` throws when the signature is bad
It doesn't return `{ valid: false }`. It throws `ERR_INVALID_SIGNATURE`. Catch it as above, or let it become a 401.
:::

## A public key per app or feature

Show users a key for your app without exposing (or correlating) their identity key:

```ts
const { publicKey } = await wallet.getPublicKey({ protocolID: [1, 'my app'], keyID: '1', counterparty: 'self' })
```

Different protocol or key ID, different key. Your app sees a stable key per user, and two apps can't link their users by it.

## Pay someone

Creating transactions needs a funded wallet, so start on testnet. The shape of the call is on the [BSV primer](https://createbsvapp.vercel.app/docs/bsv-primer#your-first-payment).

## Test these without a wallet

Every call above works on a `ProtoWallet` with a random key, which is how this page was verified. Copy the pattern from [Testing without a wallet](https://createbsvapp.vercel.app/docs/testing):

```ts
import { PrivateKey, ProtoWallet } from '@bsv/sdk'
const alice = new ProtoWallet(PrivateKey.fromRandom())
```
