Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions authentication/user-management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -234,28 +234,43 @@ Restore access for a suspended user:
</Step>
</Steps>

### Deleting users
### Removing users

Admins can remove users from the instance through the admin panel. The removal process is designed to be safe: it deactivates the user's account first, invalidates all active sessions, and rejects any new token issuance for the user before the account is fully removed.

<Warning>
Deleting a user is permanent and cannot be undone. All user data, including connected accounts and activity history, will be removed.
Removing a user is permanent and cannot be undone. All user data, including connected accounts and activity history, will be removed.
</Warning>

To delete a user:
To remove a user:

<Steps>
<Step title="Select user">
Navigate to the user's detail page
<Step title="Navigate to admin users">
Go to `/admin/users`
</Step>

<Step title="Click delete">
Click "Delete User" button
<Step title="Select the user">
Click on the user you want to remove
</Step>

<Step title="Confirm deletion">
Type the user's email to confirm deletion
<Step title="Click remove">
Click the "Remove user" button on the user's detail page
</Step>

<Step title="Confirm removal">
Confirm the action in the dialog
</Step>
</Steps>

The removal process:

1. Marks the user as inactive, preventing new logins immediately
2. Invalidates all active sessions so existing sessions cannot be used
3. Rejects token issuance for the inactive user
4. Removes the user account and associated data

If a session creation attempt occurs for a deleted user after removal, it fails closed — the request is rejected rather than creating a partial session.

## User security settings

### Connected accounts in user settings
Expand Down
70 changes: 70 additions & 0 deletions clients.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
title: Client overview
description: Choose the right entry point for a person, device, or integration connecting to Sure
---

Sure is centered on a Rails server. Every client connects to that server and uses the same accounts, users, authentication rules, and financial data. A client can be a browser, a native app, a mobile app, an automation script, or an LLM agent using Sure's MCP endpoint.

## Client overview

| Client | Best for | Status | Entry point |
| --- | --- | --- | --- |
| Web app | Full everyday use, administration, and self-hosted access from any modern browser | Primary client | Run the Rails app and visit the server URL. For local development, use `bin/dev` and open `http://localhost:3000`. |
| macOS desktop app | People who want Sure in a native Mac window with system app chrome and deep-link handling | Native shell around the web app | See the [desktop README](https://github.com/we-promise/sure/blob/main/desktop/README.md) |
| Mobile app | Basic mobile access on iOS and Android, currently focused on login and account balances | Flutter companion app | See the [mobile README](https://github.com/we-promise/sure/blob/main/mobile/README.md) |
| LLM agents and assistants | Claude Desktop, GPT agents, local agents, or custom tools that need structured access to Sure data | MCP endpoint for external AI clients | See [MCP server](/development/mcp) |
| Custom API clients | Scripts, services, importer experiments, dashboards, or other integrations | HTTP API | See the [API reference](/api-reference) |

## Web app

The web app is the complete Sure experience. It is the right default when a person wants to use Sure directly, manage settings, connect providers, review transactions, or work with features that may not yet be available in native clients.

For self-hosting, start with the [Docker hosting guide](/self-hosting). For local development, run the Rails app and visit the local server URL:

```sh
bin/dev
```

```text
http://localhost:3000
```

The web app also serves as the surface rendered by the macOS desktop app.

## macOS desktop app

The macOS desktop app is a Tauri 2 shell that renders the full Sure web app in a native Mac window. On first launch, it asks for the Sure server URL, checks the server health endpoint, and then loads the normal sign-in flow.

Use it when someone wants a desktop app experience without a separate desktop data model. It uses the same server, authentication, MFA, and permissions as the browser.

## Mobile app

The mobile app is a Flutter companion app for iOS and Android. It connects to a Sure server through the API and currently focuses on core mobile flows such as authentication and viewing account balances.

Use it when someone needs phone access and the feature set they need is available in the mobile client. For the full application surface, use the web app.

## LLM agents and MCP clients

LLM agents are clients too. Sure exposes a Model Context Protocol endpoint for external assistants and agent runtimes that need structured access to financial data.

Use MCP when a person wants an assistant such as Claude Desktop, a GPT agent, or a custom local agent to query Sure directly instead of copying data into a chat window. MCP access is configured server-side with a bearer token and a specific Sure user email.

Because this gives the assistant read access to that user's family data, treat the MCP token like a production secret and only connect assistants and providers the user trusts.

See [MCP server](/development/mcp) for setup and protocol details.

## Custom API clients

Custom clients can call Sure's HTTP API directly. This is the right path for scripts, services, import experiments, dashboards, and integrations that do not need an MCP-compatible agent interface.

API requests can authenticate with a user-generated `X-Api-Key` header, or with OAuth2 bearer tokens from Sure's Doorkeeper authorization server for registered app clients.

## Choosing a client

- Start with the web app when a person needs the complete Sure experience.
- Use the macOS desktop app when they want the web app wrapped in a native Mac application.
- Use the mobile app for iOS or Android access to supported mobile flows.
- Use MCP for LLM agents and assistant runtimes.
- Use the HTTP API for custom software integrations.

All clients should connect to a Sure server the user controls or trusts. Native, mobile, API, and MCP clients do not replace the server; they are different ways to access it.
5 changes: 3 additions & 2 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"index",
"quickstart",
"guide",
"clients",
"self-hosting",
"self-hosting-helm"
]
Expand Down Expand Up @@ -77,10 +78,10 @@
"providers/plaid",
"providers/redbark",
"providers/snaptrade",
"providers/binance",
"providers/coinbase",
"providers/coinstats",
"providers/sophtron"
"providers/sophtron",
"providers/onchain-wallets"
]
},
{
Expand Down
173 changes: 173 additions & 0 deletions providers/onchain-wallets.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
---
title: On-chain wallets
description: Track self-custody Bitcoin, EVM, and Solana wallets by public address — no key or seed phrase required
---

Sure can track wallets you hold the keys to — Bitcoin, six EVM networks, and Solana — from their **public addresses only**. Nothing is signed, no key or seed phrase is ever entered, and no API key is required for any chain.

## What gets tracked

One Sure account is created per **asset**, per **address**, per **network**. A wallet holding ETH and USDC on Ethereum becomes two accounts, both Crypto accounts with the "wallet" subtype.

For each asset Sure records:

- The **quantity** held, read from the chain
- A **holding** valued at the current price, or at zero when no price is available
- The **transfers** in and out, as investment trades when the price for that day is known, so cost basis and the value chart reconstruct back to acquisition. When that day's price is not known, the transfer still appears as an excluded, zero-amount entry — and it is upgraded to a trade on the first sync after the price becomes available

Balances are read-only and always derived from the chain. Editing them by hand is pointless: the next sync overwrites them.

## Prices need two settings, not one

On-chain data sources report **quantities, not values**. Prices come from Sure's market data providers, and the only provider that can quote bare crypto symbols is **Binance public** (keyless).

If no crypto-capable market data provider is enabled, every on-chain wallet is tracked by quantity and **valued at zero**. This is the most common support report for this feature, and it is a settings issue rather than a sync failure.

The settings panel and the linking modal both warn you before you link anything. On a self-hosted instance an admin can fix it from the warning itself with **Enable crypto prices** — that adds `binance_public` to the enabled providers and leaves the others alone. Otherwise, enable it under **Settings → Self hosting → Market data providers**, or set `SECURITIES_PROVIDERS` to a comma-separated list including `binance_public`.

### Exchange rates for non-USD families

The crypto provider quotes in USD, so valuing a wallet in any other family currency needs an exchange rate. Sure's default exchange rate provider (`twelve_data`) requires an API key. With no key and a non-USD family, every on-chain wallet is valued at zero for this second, separate reason.

Set `EXCHANGE_RATE_PROVIDER` (or **Settings → Self hosting**) to a provider you can use; `frankfurter` needs no API key. The linking UI warns about this gap specifically, naming your currency. A USD family never sees this warning.

When an asset ends up valued at zero for either reason, it is recorded in **Settings → Debug logs** under the `onchain_wallet` provider with the reasons listed.

## Data sources

| Network | Source | Key required | Override |
|---|---|---|---|
| Bitcoin | [mempool.space](https://mempool.space) REST API | No | `MEMPOOL_SPACE_URL` |
| Ethereum | Blockscout (`eth.blockscout.com`) | No | `BLOCKSCOUT_ETHEREUM_URL` |
| Base | Blockscout (`base.blockscout.com`) | No | `BLOCKSCOUT_BASE_URL` |
| Arbitrum | Blockscout (`arbitrum.blockscout.com`) | No | `BLOCKSCOUT_ARBITRUM_URL` |
| Optimism | Blockscout (`optimism.blockscout.com`) | No | `BLOCKSCOUT_OPTIMISM_URL` |
| Polygon | Blockscout (`polygon.blockscout.com`) | No | `BLOCKSCOUT_POLYGON_URL` |
| Gnosis | Blockscout (`gnosis.blockscout.com`) | No | `BLOCKSCOUT_GNOSIS_URL` |
| Solana | Public JSON-RPC (`api.mainnet-beta.solana.com`) | No | `SOLANA_RPC_URL` |
| Solana token names | Jupiter token search (`lite-api.jup.ag`) | No | `SOLANA_TOKEN_LIST_URL` |

Every override expects the base URL of a compatible instance, without a trailing slash — useful if you run your own indexer or node, or if a public endpoint rate limits you.

### Optional Etherscan key

Ethereum, and only Ethereum, can be read through Etherscan instead of Blockscout. A key buys nothing except a higher rate limit. Add it under **Settings → Providers → On-chain wallets → Advanced**; it is stored encrypted, per family.

A key only moves **transfer history** onto Etherscan. Balances and network detection always come from the keyless indexer. Leave the field empty unless you are being rate limited.

## Rate limits and request cost

All the default endpoints are free and shared, so they throttle. Per sync, per address, the cost is roughly:

| Network | Requests per sync |
|---|---|
| Bitcoin | 1 + up to 10 history pages |
| EVM | 1 summary + up to 10 pages of transfers + up to 10 pages of token transfers |
| Solana | ~2 + up to 25 transaction reads |

History is capped by default at 10 pages per source and at 25 transactions for Solana. Wallets with more history keep their **current balance correct** — balances come from an address summary, never from history — but their oldest transfers are not imported.

Raise both caps with `ONCHAIN_HISTORY_MAX_PAGES` (default 10, maximum 200); the Solana budget scales proportionally.

**History is best effort; balances are not.** If a source refuses or times out on paginated history, the balances are still recorded and the history is marked incomplete. When a cap is hit, the affected address says so in **Manage wallets**, and the event is recorded in **Settings → Debug logs** under the `onchain_wallet` provider.

### Token limits per address

Real addresses are airdrop dumping grounds. One read surfaces at most **200 tokens** per address, settable with `ONCHAIN_MAX_TOKENS_PER_ADDRESS` (maximum 5,000).

The native coin is never affected, and anything already tracked keeps syncing regardless. On EVM networks the tokens kept are ranked by market cap, so real assets survive the cap and airdrops fall off the end. On Solana the order is by mint address — arbitrary, but identical between syncs, which stops the cap from reshuffling a wallet every night.

## Linking a wallet

Go to **Settings → Providers → On-chain wallets → Add wallet**.

<Steps>
<Step title="Paste the public address">
Leave the network on "Detect automatically" unless you know which one you want.
</Step>
<Step title="Confirm the network">
If the address format belongs to several networks — every `0x` address is valid on all six EVM networks, and Bitcoin's Base58 shape overlaps Solana's — Sure probes each and asks you to choose, marking the ones where it found activity.
</Step>
<Step title="Choose assets to track">
The native coin and assets the data source treats as notable are pre-ticked. "Notable" means a priced holding worth more than a dollar on EVM networks, or a place on Solana's verified token list. You can still track anything listed; unpriceable assets show a quantity and a value of zero.
</Step>
</Steps>

Nothing is imported that you did not tick.

## Managing a wallet

Go to **Settings → Providers → On-chain wallets → Manage wallets**.

- **Review tokens** — reopens the asset selection with the address unchanged. Use this to start tracking a token that arrived later, or stop tracking one you no longer want.
- **Stop tracking** (per asset) — drops one asset.
- **Change address** — corrects the address while keeping the accounts, holdings, and balance history attached to it.
- **Disconnect wallet** — drops every asset at one address.

Disconnecting never deletes an account. The provider link is removed, holdings are detached, and what you can see stays as a manual account that no longer updates. Delete the account itself if you want it gone.

An address can only be tracked once per network. To change which assets are tracked, use **Review tokens** rather than adding the address again.

## Limitations

**Only tokens the crypto price provider quotes get a value, and it quotes by symbol.** Valuation goes through a `CRYPTO:<SYMBOL>` ticker, and a symbol is not a token's identity — its contract is. In practice the provider covers major assets and little else: measured on a real Ethereum address, of its ten largest token positions it quoted two. The other eight — including holdings worth roughly $406,000, $141,000 and $74,000 — showed a value of zero while their quantities were tracked correctly.

**A zero next to a token you know is worth something almost always means the provider does not list that token, not that the balance is wrong.** Check the quantity, which is read straight from the chain. Native coins (BTC, ETH, SOL, POL, XDAI) and large-cap tokens are the well-covered case.

**DeFi positions are not seen at all.** Staked ETH, liquidity-pool tokens, lending positions, and Solana stake accounts are invisible. A wallet holding most of its value in a staking or lending protocol will report a fraction of it.

**Bitcoin is one address at a time.** Extended keys (`xpub`, `ypub`, `zpub`) are not supported and are rejected as addresses. Most Bitcoin wallets are HD wallets, where one extended key derives thousands of addresses and change is sent to derived ones. Tracking a single address of such a wallet reports only that address's balance.

**Solana token names depend on a token list.** RPC returns mints, not names, so names come from Jupiter's token search — and only for mints it reports as *verified*. An unverified or unknown mint keeps a label built from its mint address and is tracked by quantity only. Names are cached for 24 hours per mint.

**Fees are not itemised.** Network fees are included in the net effect of each transfer rather than recorded separately. On Solana, native balance changes below 0.0001 SOL are treated as fees and ignored.

**Bridged assets are normalised.** USDC.e, USDbC, USDT0, WETH, and similar 1:1-redeemable forms are tracked as their canonical asset, so the same asset held on two networks is one Security rather than two.

**Prices are daily and USD-quoted.** Values are converted into your family currency using Sure's exchange rates. A transfer whose day has no price becomes a zero-amount excluded entry rather than a trade.

**NFTs are not tracked.** They are filtered out by token standard, so an NFT is never mistaken for a balance of one fungible token.

## Troubleshooting

<AccordionGroup>
<Accordion title="Every wallet shows a value of zero">
Either no crypto-capable market data provider is enabled, or your family currency is not USD and no exchange rate provider is configured. Both are covered in the "Prices need two settings" section above, and the linking UI says which one applies.
</Accordion>

<Accordion title="One token shows zero while the others in the same wallet are fine">
Different cause: the price provider does not quote that token. Pricing is by symbol and covers major assets, so long-tail tokens are tracked by quantity and valued at zero. There is no setting that changes this.
</Accordion>

<Accordion title="A Bitcoin balance is much lower than my wallet app shows">
You are tracking one address of an HD wallet. See "Limitations" above.
</Accordion>

<Accordion title="Sync says the explorer could not be reached">
The public endpoint is down, throttling you, or too slow to answer. Retry later, or point the relevant `*_URL` override at your own instance.
</Accordion>

<Accordion title="Solana shows balances but no transfers">
The free endpoint throttles the history methods; balances are kept and the history is marked incomplete. Set `SOLANA_RPC_URL` to your own node or a paid endpoint to get the transfers.
</Accordion>

<Accordion title="A token I received is not showing up">
New assets are never imported automatically. Use **Review tokens** and tick it.
</Accordion>

<Accordion title="A Solana token shows as SPL:abcd…wxyz">
The token list does not vouch for that mint, so Sure will not name or price it. Tick it anyway to track the quantity.
</Accordion>

<Accordion title="Transfers appear with a value of 0 and are excluded from totals">
No price was available for that date yet. Once market data covers the range, the next sync upgrades those entries to trades automatically — they keep their identity, so nothing is duplicated. If they stay at zero, the date is outside what your market data provider can serve.
</Accordion>

<Accordion title="Manage wallets says the history is incomplete">
The address has more transfers than one sync reads. Balances are unaffected. Raise `ONCHAIN_HISTORY_MAX_PAGES` if you need the full history and can afford the extra requests.
</Accordion>

<Accordion title="Balances are correct but nothing updates">
Syncs only rewrite an account when the chain actually changed — an idle wallet produces no writes at all. Check **Settings → Providers** for the last sync time, and **Settings → Debug logs** filtered to the `onchain_wallet` provider for recorded failures.
</Accordion>
</AccordionGroup>
Loading