Pay for a Bitrefill product (gift card, top-up, eSIM) with the worst-performing crypto in your portfolio — the asset with the lowest expected return μ — then retune the portfolio without it.
The agent logic lives in SKILL.md; this README is the operator's quick-start.
Purchase mechanics run on the Bitrefill MCP (the mcp__bitrefill__* tools), connected separately
(see Install the skill).
The fastest path uses the live hosted server at https://qupick.quip.network — you don't run any
backend yourself. Five steps from clone to first run:
-
Install the skills. Install
qupick(this repo) and thebitrefillplugin (from upstream) — see Install the skill. -
Configure your defaults. Copy the example and fill it in (see Configure):
cp skills/qupick/config.example.json skills/qupick/config.json
Set at least
defaults.nameanddefaults.email— registration emails your API key to that address, and bothnameandemailmust be unique on the server. -
Point Claude Code at the hosted MCP server. Copy the example and set the
urlto the hosted endpoint. The API key goes directly in theAuthorizationheader — Claude Code does not expand${VAR}in.mcp.jsonheaders, so an environment variable won't reach the server. You have no key yet, so leave a placeholder for now (.mcp.jsonis gitignored, so the real key is safe here later):cp .mcp.json.example .mcp.json
{ "mcpServers": { "qupick": { "type": "http", "url": "https://qupick.quip.network/mcp", "headers": { "Authorization": "Bearer REPLACE_WITH_YOUR_KEY" } } } } -
Register to get your API key. With the placeholder still in place, start Claude Code and ask the agent to proceed.
register_agentis a public tool — it works without a valid key — and it is called with yourconfig.defaults(name, email, sliders); the server emails your API key to that address (it is never shown in the response). A repeat email or name returns409with detailemail already registered/name already taken(oremail or name already taken). -
Paste the key and reconnect. Put the emailed key into
.mcp.json, replacing the placeholder in theAuthorizationheader:"headers": { "Authorization": "Bearer <key-from-email>" }
Then reload the server with
/mcpinside Claude Code. The per-agentmcp__qupick__*tools now authenticate and the agent runs its firstoptimize.
That's it — continue to Use it. To run the backend yourself instead of using the hosted server, see Development.
- The qupick MCP server — the portfolio backend, registered via
.mcp.jsonand exposingmcp__qupick__*tools. Use the hosted server athttps://qupick.quip.network/mcp(above) or run it locally athttp://127.0.0.1:8000/mcp(see Development). - The Bitrefill MCP connected (
https://api.bitrefill.com/mcp, OAuth or API key) — qupick's purchase layer. Themcp__bitrefill__*tools drive product search, balance reads,buy-products, and invoice polling. Add it withclaude mcp add(see Install the skill); the Bitrefill REST API key is an alternative for the balance probe. - A funding source the waterfall can draw on: a pre-funded Bitrefill account balance (USD, EUR, and/or the worst-performing asset) and/or a funded on-chain wallet for that asset. The funding order is configurable (see Configure).
- A
skills/qupick/config.json— copyskills/qupick/config.example.jsonand fill it in. Without it the skill still runs, but fully interactively (it asks for name, denomination, and pays on-chain only).
This repo ships the qupick skill. Its purchase layer is the Bitrefill MCP — connect that
separately; the mcp__bitrefill__* tools are what qupick actually calls. Nothing from Bitrefill is
vendored here.
-
Bitrefill MCP (required). Connect the hosted MCP; authenticate via OAuth (
/mcp) or API key:claude mcp add --transport http bitrefill https://api.bitrefill.com/mcp --scope user
Optional: the upstream bitrefill plugin bundles the same MCP plus the bitrefill skill (multi-channel purchase guidance). Run these as slash-commands inside Claude Code (not a shell):
/plugin marketplace add bitrefill/agents→/plugin install bitrefill@bitrefill-skills→/reload-plugins(https://github.com/bitrefill/agents). qupick needs only the MCP, not the skill. -
qupick (this repo). Claude Code discovers skills under
.claude/skills/:mkdir -p .claude/skills cp -R skills/qupick .claude/skills/
.claude/ is gitignored — the skill install is local, not committed. The one tracked exception is
backend/.claude/settings.json, the shared permission allowlist (see
Permissions & approvals).
Copy the example config and edit it:
cp skills/qupick/config.example.json skills/qupick/config.jsonconfig.json is gitignored (it holds your real email). Identity is not in the config — the
agent's API key lives in the .mcp.json Authorization header (see
Register & set up). Fields:
| Field | Purpose |
|---|---|
defaults |
name / email / country / sliders used when creating a new agent. |
funding.priority |
Settlement order. Default ["account_match", "onchain_match", "account_fiat"]. |
funding.fee_buffer_pct |
Coverage buffer over the sticker price (default 2). |
funding.on_shortfall |
reject (stop) or confirm (warn and ask) when nothing covers the price. |
denomination.policy |
smallest_gte auto-picks the smallest package ≥ the requested amount. |
backend.marketDataSource |
MARKET_DATA_SOURCE used when the skill auto-starts the backend. Default synthetic (offline, deterministic). |
Funding waterfall. The worst performer (min(μ)) is always computed. The bill is then settled by
the first source in funding.priority that covers the price:
account_match— Bitrefill account balance held in the worst-performing asset → sells it → retunes. Bitrefill sub-accounts are limited toXBT/USD/EUR, so this only applies when the worst performer is BTC; for any other asset it is skipped and the waterfall falls through.onchain_match— on-chain wallet holdings of the worst-performing asset → sells it → retunes.account_fiat— Bitrefill USD/EUR balance → settles without selling crypto → no retune.
Reorder or drop tokens to change behaviour — e.g. ["account_fiat", "account_match", "onchain_match"]
to spend fiat first, or drop account_fiat to only ever sell crypto.
This config plus the permission allowlist in backend/.claude/settings.json make a run stop in
exactly one place — the purchase approval. See Permissions & approvals.
The skill is designed to interrupt you in exactly one place — the purchase approval in step 6. Everything else it does is read-only. To get that clean single-stop experience, pre-approve the read-only tools so they don't prompt, and keep the spend/mutation tools gated.
The allowlist lives in backend/.claude/settings.json — this is the project directory Claude
Code resolves from when you run the skill from backend/. It is the one file under .claude/ that
is git-tracked (via a .gitignore exception); settings.local.json is for personal, machine-local
overrides and stays ignored.
Pre-approve (read-only — safe in permissions.allow):
| Entry | Why it's safe |
|---|---|
mcp__qupick__ping_backend, get_agent, get_market, get_leaderboard |
backend reads — liveness, config, holdings, scoreboard |
mcp__bitrefill__search-products, get-product-details |
catalog search + pricing/accepted methods |
mcp__bitrefill__get-invoice-by-id, list-invoices |
invoice polling for on-chain settlement |
Bash(curl https://api.bitrefill.com/v2/accounts/balance*) |
read-only account-balance probe |
Read(.../skills/qupick/**) |
reads config.json and the skill files |
Keep gated (in permissions.ask, never allow):
| Entry | Why it must prompt |
|---|---|
mcp__bitrefill__buy-products |
real-money purchase — the single, non-negotiable human stop |
mcp__qupick__optimize |
the irreversible retune — drops the sold asset from the basket |
mcp__qupick__register_agent, submit-prepayment-step, update-order |
account/order mutations |
ask rules take precedence over allow, so even if a broader allow pattern would match, these
always surface a prompt.
The flow works end-to-end under Claude Code's auto-accept modes — but for a real-money skill that's the wrong default:
bypassPermissions("dangerously skip permissions") removes the spend gate. Nothing prompts, sobuy-productsand the irreversibleoptimizeretune would run unattended. Don't run this skill in that mode.- Prefer default mode + the read-only allowlist above. You get the same uninterrupted run for every read-only step, while purchases and retunes still stop for explicit approval — the gate the skill's safeguards depend on.
- The
permissions.askentries above are your safety net: they force a prompt for purchases and retunes regardless of the allowlist (but they do not overridebypassPermissions, which is why that mode is off-limits here).
To regenerate or extend the allowlist from your own usage, run the /fewer-permission-prompts
helper — it scans recent transcripts for read-only calls and proposes entries.
Portability quirk: the two
Read(...)rules use absolute paths for this machine (/home/konrad/Quip/qubitrefill/...). Becausesettings.jsonis now committed, anyone cloning to a different path must update those two lines (or delete them and accept a single prompt on the first config read). The MCP-tool andcurlentries are fully portable.
Ask the agent in natural language, e.g.:
"Buy a $20 Steam gift card and pay with my worst-performing crypto."
The skill then runs the flow from SKILL.md:
- Read config —
skills/qupick/config.json(defaults, funding order). Missing/malformed → fully interactive fallback, no crash. - Available currencies — static map of which holdings are spendable. Of the 11 basket tickers,
only BTC, ETH, SOL, USDC, USDT have a
buy-productsrail; the other six (BNB, XRP, DOGE, ZEC, ALGO, FIL) are held for diversification but can't fund a purchase. Stablecoins are multi-rail (usdc_base/usdc_solana/…,usdt_trc20/…), matched against the product's accepted methods live. - Check + seed agent (MCP) —
ping_backend; if the qupick tools are missing, offer to start the server. Thenget_agent(success → reuse the basket), orregister_agentfromconfig.defaults→ put the emailed/console key in.mcp.json'sAuthorizationheader, reconnect MCP, thenoptimizefor the first solve. - Pick product (MCP) —
search-products→product-detailsfor price + acceptedpayment_methods;denomination.policyauto-selects the package. - Market (MCP) —
get_marketfor per-asset μ, units, USD value. - Select + fund — compute the worst performer (
min(μ)) over held, product-accepted crypto, then read account balances (theaccount_balancesblock fromproduct-details, falling back toGET /accounts/balance) and resolvefunding.priorityto the first source covering the price. - Confirm + buy (MCP) — the agent stops for your explicit approval at a fully-resolved
screen (worst performer + chosen funding source), then buys: instant
balancepay, or an on-chain link it polls tocomplete. Surfaces the redemption code. - Retune (MCP) —
optimizewith the basket minus the spent ticker — only when the worst performer was actually sold (account_match/onchain_match). Fiat settlement leaves the portfolio unchanged.
/market ranked by μ (worst first):
BTC crypto μ=-0.002567 $230.67 ← worst performer (always selected)
SOL crypto μ=-0.000341 $225.07
USDC crypto μ=+0.000046 $2272.70
ETH crypto μ=+0.000129 $229.61
Product: Steam USD $20 ($21.60, +2% buffer → $22.03) · accepts bitcoin/ethereum/solana/usdc_base
Worst: BTC (μ=-0.0026)
Waterfall: account_match → Bitrefill account BTC $60 covers $22.03 ✓
Settle: Bitrefill account BTC · sells it ✓ · will retune
Buy: payment_method="balance", balance_currency="XBT" → complete → redemption code
Retune: drop BTC, re-optimize over the remaining 10 currencies
- The agent never buys without explicit approval — it always pauses at step 6.
- Codes deliver instantly and are non-refundable; treat redemption codes as cash and redeem ASAP.
- Use a dedicated, low-balance wallet. Never expose high-balance accounts or wallet seeds. (If you
install the optional bitrefill plugin, its
safeguards.mdcovers per-host hardening too.) - The step-7 retune is irreversible — the spent asset leaves the basket until you re-add it.
To work on the backend or run fully offline, run the server yourself instead of using the hosted one.
The backend serves both its REST API and the qupick MCP server (mounted at /mcp) from one
process; start it before the Claude session so the mcp__qupick__* tools register.
Docker (Postgres + backend together):
docker compose up -d --buildThis brings up Postgres and the backend (image built from backend/Dockerfile), publishing the
server on http://127.0.0.1:8000. The container defaults to MARKET_DATA_SOURCE=synthetic and
GUROBI_IN_RACE=0 (SA is the CPU solver — no Gurobi licence or D-Wave token needed).
Local (uv), Postgres from compose:
docker compose up -d db
cd backend
MARKET_DATA_SOURCE=synthetic uv run uvicorn backend.api.app:app --workers 1 --port 8000Either way, wait until GET http://127.0.0.1:8000/healthz returns {"ok": true}. If the server is
down at session start the skill offers to start it backgrounded (allowlisted synthetic command) —
but the MCP tools only appear after you reconnect the server (run /mcp in Claude Code).
First-solve cold start: the very first
optimizecall can return503 no feasible solution ... before deadlinewhile the D-Wave/Gurobi libs warm up. Just retry once — subsequent solves are sub-10ms.
Point .mcp.json at the local server — same as the hosted setup,
but with the local URL (the key still goes directly in the header, not via an environment variable):
{ "mcpServers": { "qupick": { "type": "http", "url": "http://127.0.0.1:8000/mcp",
"headers": { "Authorization": "Bearer <your-key>" } } } }Registration works the same way, except a local backend with no SMTP_PASSWORD set does not send
email — instead the key is printed to the backend console as
[email:console] API key for … : <key> (docker compose logs backend). Paste that value into
.mcp.json's Authorization header and reconnect (/mcp).