From 9b3464eec4e09a1dd0e7f0612be678ef7343730f Mon Sep 17 00:00:00 2001 From: soundsng Date: Mon, 27 Jul 2026 11:45:57 +0000 Subject: [PATCH] docs(product-audit): add knowledge-base entries for #250, #251, #252, #254 Adds 4 documentation files under bridgelet-product-audit/. Documentation-only; no changes to frontend/ or mobile/. --- .../glossary/claim-token-and-claim-url.md | 45 +++++++++++++++++ .../glossary/connected-wallet-persistence.md | 50 +++++++++++++++++++ ...ighter-vs-lobstr-vs-generated-tradeoffs.md | 45 +++++++++++++++++ .../glossary/wallet-connection-types.md | 45 +++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 bridgelet-product-audit/glossary/claim-token-and-claim-url.md create mode 100644 bridgelet-product-audit/glossary/connected-wallet-persistence.md create mode 100644 bridgelet-product-audit/glossary/freighter-vs-lobstr-vs-generated-tradeoffs.md create mode 100644 bridgelet-product-audit/glossary/wallet-connection-types.md diff --git a/bridgelet-product-audit/glossary/claim-token-and-claim-url.md b/bridgelet-product-audit/glossary/claim-token-and-claim-url.md new file mode 100644 index 0000000..3e13917 --- /dev/null +++ b/bridgelet-product-audit/glossary/claim-token-and-claim-url.md @@ -0,0 +1,45 @@ +# Claim Token and Claim URL + +## Canonical definitions + +`docs/GLOSSARY.md` holds the canonical short definitions — a **Claim Token** is +the unique token that lets a recipient securely claim funds, and a **Claim URL** +is the web link containing that token. Those entries are the source of truth for +the terms themselves; this page does not restate them, it describes the frontend +flow that produces and consumes them. + +## Which route renders the claim experience + +The claim UI lives at a dynamic Next.js route keyed on the token itself: + +``` +frontend/app/claim/[token]/ +├── page.tsx # route entry +├── loading.tsx # loading state +└── claim-page-client.tsx # client component doing the real work +``` + +The token is therefore a **path segment**, not a query parameter — a claim URL +looks like `/claim/`. The `[token]` segment is what the recipient's link +carries, and `claim-page-client.tsx` is where the claim is actually presented and +attempted. + +A separate sandbox page exists at `frontend/app/sandbox/claim-test/` for +exercising the flow without a real token. + +## How this ties back on-chain + +Opening a claim URL is the product-level entry point to the on-chain sweep. The +token identifies the **ephemeral account** holding the funds (see +`docs/GLOSSARY.md`), and completing a claim is what triggers the sweep of that +account's balance to the recipient's destination wallet. + +The on-chain mechanics of ephemeral accounts and sweeping are documented in the +`bridgelet-audit/` initiative for `bridgelet-core`; this repo's concern stops at +rendering the claim and calling the API. + +## Related + +- [`claim-url-security-properties.md`](../integration-notes/claim-url-security-properties.md) + — what a claim URL guarantees as a bearer credential. +- [`sender-vs-recipient-auth-models.md`](./sender-vs-recipient-auth-models.md) diff --git a/bridgelet-product-audit/glossary/connected-wallet-persistence.md b/bridgelet-product-audit/glossary/connected-wallet-persistence.md new file mode 100644 index 0000000..c2282b7 --- /dev/null +++ b/bridgelet-product-audit/glossary/connected-wallet-persistence.md @@ -0,0 +1,50 @@ +# Connected Wallet Persistence + +How a connected wallet survives a page reload, per `frontend/lib/wallet.ts`. + +## The storage key and shape + +Persistence is plain `localStorage` under a single key: + +```ts +const STORAGE_KEY = 'bridgelet_wallet'; +``` + +`persistWallet()` writes `JSON.stringify(wallet)`, where `wallet` is a +`ConnectedWallet`. That type has exactly two fields: + +| Field | Type | Example | +|---|---|---| +| `publicKey` | `string` | `G...` (Stellar public key) | +| `type` | `WalletType` | `'freighter' \| 'lobstr' \| 'generated'` | + +## What is *not* stored + +**No secret key material is written to `localStorage` by this path.** The +`ConnectedWallet` type has no field for it. Note that `generateNewWallet()` does +return a `secretKey` alongside the wallet, but that secret is returned to the +caller only — `persistWallet()` never receives it and therefore cannot store it. +This is a factual record of current behaviour, not a guarantee about future +callers. + +## Corrupted or missing values + +`loadPersistedWallet()` wraps the read in `try`/`catch` and returns `null` on any +failure: + +```ts +try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return null; + return JSON.parse(raw) as ConnectedWallet; +} catch { + return null; +} +``` + +So a malformed JSON value degrades silently to "no wallet connected" rather than +throwing — the user sees a disconnected state with no error explaining why. +`clearPersistedWallet()` removes the key outright. Note the cast is unchecked: a +*well-formed* JSON value of the wrong shape is returned as-is rather than +rejected. See +[`../runbooks/clear-stale-persisted-wallet.md`](../runbooks/clear-stale-persisted-wallet.md). diff --git a/bridgelet-product-audit/glossary/freighter-vs-lobstr-vs-generated-tradeoffs.md b/bridgelet-product-audit/glossary/freighter-vs-lobstr-vs-generated-tradeoffs.md new file mode 100644 index 0000000..b53997f --- /dev/null +++ b/bridgelet-product-audit/glossary/freighter-vs-lobstr-vs-generated-tradeoffs.md @@ -0,0 +1,45 @@ +# Freighter vs LOBSTR vs Generated — Tradeoffs + +The practical tradeoffs behind the three `WalletType` values. For what each value +*is*, see [`wallet-connection-types.md`](./wallet-connection-types.md). + +## Freighter + +**Requires** the user to have installed the Freighter browser extension. That is +a real drop-off point: `connectFreighter()` throws +`"Freighter extension not found. Please install it from freighter.app and refresh."` +for anyone who hasn't. + +In exchange it has the strongest security model of the three. The secret key +never leaves the extension — the app calls `signTransaction()` and receives back +signed XDR, so Bridgelet code never handles key material. `signFreighterTransaction()` +tolerates several response shapes (`signedTxXdr`, `signedTxXDR`, `xdr`), which +suggests the extension's response format has varied across versions. + +**Best for:** crypto-literate senders on desktop. + +## LOBSTR + +Currently a **manual paste flow**, not a live connection — LOBSTR ships no +browser JS SDK comparable to Freighter's. The user copies their public key out of +the LOBSTR app and pastes it in. + +The tradeoff is a manual-entry error surface: a mistyped or truncated key is only +caught by format validation, and the app never gets signing capability — it learns +an address, nothing more. It also can't prove the user controls that address. + +**Best for:** mobile-first users, accepting reduced assurance. + +## Generated + +Maximum convenience — one call to `generateNewWallet()` and the user has a +keypair, no install and no existing wallet needed. This is the path that serves +Bridgelet's core "recipient has no wallet" use case. + +The cost is key custody. The function returns a raw `secretKey` string, and +whoever receives it becomes responsible for storing it and for the user's ability +to recover funds. Lose it and the funds are unrecoverable. See +[`generated-wallet-key-custody.md`](../integration-notes/generated-wallet-key-custody.md) +— this path has no caller yet, and the custody question is unresolved. + +**Best for:** first-time recipients — once custody is specified. diff --git a/bridgelet-product-audit/glossary/wallet-connection-types.md b/bridgelet-product-audit/glossary/wallet-connection-types.md new file mode 100644 index 0000000..d85ffa0 --- /dev/null +++ b/bridgelet-product-audit/glossary/wallet-connection-types.md @@ -0,0 +1,45 @@ +# Wallet Connection Types + +The `WalletType` union in `frontend/lib/wallet.ts` has three values. Each means +something quite different in practice today. + +```ts +export type WalletType = 'freighter' | 'lobstr' | 'generated'; +``` + +A connected wallet is represented as `ConnectedWallet { publicKey, type }`. + +## `freighter` + +A live connection to the [Freighter](https://freighter.app) browser extension, +established by `connectFreighter()`. The function checks `freighter.isConnected()` +first and throws a descriptive "extension not found" error if the extension is +absent, then calls `requestAccess()` (which opens the extension popup) and reads +the address back via `getAddress()`. + +## `lobstr` + +Not a live SDK connection. LOBSTR has no browser-extension JS SDK equivalent to +Freighter's, so `connectLobstr()` immediately throws the sentinel string +`USE_PASTE_FLOW`. The intent is that the UI catches this and renders a +paste-your-public-key step instead. See +[`lobstr-paste-flow-not-implemented.md`](../integration-notes/lobstr-paste-flow-not-implemented.md) +for whether that handling actually exists. + +## `generated` + +A brand-new Stellar keypair created client-side by `generateNewWallet()`, which +dynamically imports `@stellar/stellar-sdk` (to avoid SSR issues) and calls +`Keypair.random()`. It returns both the wallet and a raw `secretKey` string. +Key-custody implications are covered in +[`generated-wallet-key-custody.md`](../integration-notes/generated-wallet-key-custody.md). + +## Relationship to the existing glossary + +This entry **extends** rather than replaces `docs/GLOSSARY.md`, which defines the +broader Stellar and product vocabulary (Account, Ephemeral Account, Custodial +Model). Read that first for the domain terms; read this for what the three +connection values concretely mean in the frontend code. + +For the decision record behind choosing wallet-based auth at all, see +`docs/sender-auth-model.md` (Option B).