# Bivvy > Bivvy is a passkey-derived Bitcoin and Nostr wallet with a browser JavaScript SDK. Apps can request access to a shared Bitcoin wallet and portable Nostr identity. Private keys stay inside Bivvy. Production origin: https://bivvy.me. Bivvy is a development release without an independent security audit. This document describes the implemented API; do not assume that arbitrary wallet methods are available. ## Documentation - [Quickstart](https://bivvy.me/docs/quickstart): Install, connect, and handle approvals. - [Bitcoin wallet](https://bivvy.me/docs/wallet): Balances, receiving, payments, and transfers. - [Nostr identity](https://bivvy.me/docs/nostr): NIP-07 signing and encryption. - [Permissions](https://bivvy.me/docs/permissions): Approval prompts and optional monthly allowances. - [Security and recovery](https://bivvy.me/docs/security): Trust assumptions, passkey requirements, and recovery. - [API reference](https://bivvy.me/docs/reference): Supported SDK methods. - [Playground](https://bivvy.me/playground): Interactive browser examples using the real approval flow. - [TypeScript declarations](https://bivvy.me/bivvy.d.mts): Public SDK types. ## Install and connect This is a browser SDK, not a server-side signing API. Load the script once: ```html ``` The script provides `window.bivvy` and installs `window.nostr` and `window.webln` only when those globals are not already present. Existing browser extensions are preserved. Use `bivvy.nostr` and `bivvy.webln` when you specifically want Bivvy. Alternatively, create an ES module client: ```js import { createBivvy } from "https://bivvy.me/bivvy.mjs"; const bivvy = createBivvy(); // Call from a user-initiated flow. Bivvy presents its connection approval UI. const { pubkey } = await bivvy.connect(); const { balance } = await bivvy.bitcoin.getBalance(); const sats = BigInt(balance); ``` All methods below are on the `bivvy` instance. The wallet namespace is `bitcoin`. ## Bitcoin API - `bitcoin.getBalance()` returns `{ balance: string }`, in satoshis. - `bitcoin.getOnchainAddress()` returns a Bitcoin on-chain deposit address. Deposits require confirmations. - `bitcoin.getTransfers(limit = 20, offset = 0)` returns `{ transfers, offset }`. Limit must be 1–100 and offset must be nonnegative. - `bitcoin.createLightningInvoice({ amountSats, memo? })` returns `{ id, invoice: { encodedInvoice } }`. The memo is at most 120 characters. Invoices expire after one hour. - `bitcoin.payLightningInvoice({ invoice, maxFeeSats? })` returns `{ id, status, paymentPreimage? }`. The default maximum fee is 100 sats; supported values are 0–100,000 sats. - `bitcoin.estimateOnchainFee({ address, amountSats, speed? })` returns `{ feeSats, expiresAt }`. It requires approval because preparing a quote can reorganize funds within the wallet. - `bitcoin.sendOnchain({ address, amountSats, maxFeeSats, speed? })` returns `{ id, status, amountSats, feeSats, txid? }`. The recipient receives the full amount; fees come from the remaining balance. `maxFeeSats` is required, in the range 0–100,000. Speed is `slow`, `standard` (default), or `fast`. Only valid Bitcoin mainnet legacy, nested SegWit, native SegWit, and Taproot addresses are accepted. Expired quotes and fees above the cap fail without sending. Amounts supplied to the SDK are positive, safe-integer numbers of satoshis. Lightning payments require a Bitcoin mainnet invoice with a fixed, whole-satoshi amount. Amountless and fractional-satoshi invoices are rejected. ```js const { invoice } = await bivvy.bitcoin.createLightningInvoice({ amountSats: 1000, memo: "Thank you" }); // Present invoice.encodedInvoice to the payer. const payment = await bivvy.bitcoin.payLightningInvoice({ invoice: "lnbc...", // Replace with a complete, valid invoice. maxFeeSats: 25 }); if (payment.paymentPreimage) { // Payment is confirmed. } else { // Submitted or pending: inspect wallet activity before taking further action. } ``` A request resolving without a payment preimage is not proof that a Lightning payment settled. A timeout may happen after submission. Never automatically retry a payment with a fresh request ID; inspect its status first. ## Personal payment links Every registered username has a public payment page at `https://bivvy.me/user/`. It renders as HTML for crawlers and works without a Bivvy account for the payer. Amounts can be entered in sats or USD. Notes are included as the Lightning invoice memo. Prefill the form with `?amount=69&unit=sats¬e=Invoice`. Supported units are `sats` (default) and `usd`; notes are limited to 120 characters. Payment pages accept 1–100,000 whole sats. USD amounts use the displayed exchange rate and round to whole sats. Loading a link never creates or pays an invoice automatically; the payer explicitly requests a QR code and pays with their Lightning wallet. ## On-chain payments ```js const payment = { address: "bc1...", amountSats: 10000 }; // Use a complete valid address. const { feeSats } = await bivvy.bitcoin.estimateOnchainFee(payment); const result = await bivvy.bitcoin.sendOnchain({ ...payment, maxFeeSats: feeSats }); // Submitted does not mean confirmed. On-chain payments need Bitcoin confirmations. ``` A fee quote is scoped to the wallet, address, amount, and speed. Quotes expire; request a new estimate when needed. Fees count toward allowances. Both payment types reserve the amount plus the maximum fee, conservatively retaining reservations on an uncertain outcome. Never automatically retry with a new request ID. ## WebLN - `webln.enable()` connects the wallet. - `webln.getInfo()` returns the Bivvy alias and supported WebLN methods. - `webln.sendPayment(invoice)` returns `{ preimage }` only for a confirmed payment. It throws `PAYMENT_PENDING` when confirmation is unavailable. - `webln.makeInvoice(amount)` or `webln.makeInvoice({ amount, defaultMemo? })` returns `{ paymentRequest }`. Amount can be a number or decimal string of satoshis. ## Nostr - `nostr.getPublicKey()` returns the shared hexadecimal public key. - `nostr.signEvent({ kind, created_at, tags, content })` returns the signed event after approval. - `nostr.getRelays()` returns relay read/write preferences. - `nostr.nip44.encrypt(pubkey, text)` and `nostr.nip44.decrypt(pubkey, text)` implement NIP-44. - `nostr.nip04.encrypt(pubkey, text)` and `nostr.nip04.decrypt(pubkey, text)` support legacy NIP-04. The same Nostr identity follows the user across connected apps. Signing and encryption require approval for each operation. ## Approval and lifecycle rules The SDK loads a Bivvy-origin iframe. Sensitive approvals use a separate, top-level Bivvy window so the embedding app cannot alter or cover the approval details. Passkey PRF and user verification are required. Do not implement a fallback that creates a random wallet when authentication fails. Private keys remain in Bivvy's memory. The host receives only the requested operation result. No SDK method exports seeds or private keys. Users can explicitly grant an optional monthly spending allowance; an app cannot grant itself permissions. Spending within an allowance still requires an unlocked iframe session, and fees count toward the cap. Await one operation at a time. Handle errors such as `BUSY`, `DISCONNECTED`, `UNAVAILABLE`, `TIMEOUT`, and `PAYMENT_PENDING`; display a useful result without silently resubmitting a payment. `disconnect()` locks and disconnects the iframe session. `destroy()` tears down the client and rejects pending requests. ## Integration boundaries Use HTTPS in production. Local development can use `http://localhost:5188` as a separate Bivvy origin through `createBivvy({ origin: "http://localhost:5188" })`; its passkeys and wallets are distinct from production. An integrating site's Content Security Policy must permit loading the SDK and framing the selected Bivvy origin. Do not frame `/wallet` or `/approve` directly; those pages deliberately refuse embedding. Bivvy uses Spark internally for the shared Bitcoin wallet. The public API only supports Lightning and on-chain Bitcoin. Spark addresses and direct Spark transfers are not exposed, nor are arbitrary Spark SDK methods, token operations, swaps, raw transfer leaves, or unilateral-exit tooling through the public SDK. Bivvy enables Spark privacy mode before allowing shared-wallet operations and fails initialization if the setting is not confirmed. This suppresses Bitcoin wallet activity from Spark's public queries and explorers. It does not hide Bitcoin on-chain data, erase previously collected records, or conceal activity from operators and counterparties. Apps cannot toggle this setting through the SDK. Names such as `luke@bivvy.me` support NIP-05 and Lightning addressing; they are not email login credentials. Creating a different passkey creates a different wallet and identity. Restoring an existing wallet requires its original passkey or compatible recovery tooling for its saved recovery words.