Repository navigation
fix(cli): say how to get a USD quote and what it must match - #61
Conversation
There was a problem hiding this comment.
Review of c0692e3 against master b25c05d (Aurelius). Backend references are voltagecloud/backend origin/master 1630b77c.
1. High: the same-currency hint tells a USD wallet to quote in the wrong direction for sends (src/input.rs:548)
The hint says "To pay or receive with a USD wallet, quote the USD amount: --unit usd --to btc." The backend says the opposite for paying: "For a USD wallet paying a known BTC amount, use that BTC amount and set to to USD" (crates/api/src/domain/quoting/routes.rs:520, and the btc_to_usd_quote example at :557: "Reuse the exact BTC amount in the payment request"). A send's amount must be BTC (payments/routes.rs:3777, try_as_msats gives "Only BTC is supported for Bolt11 payments"). validate_quote_amount (payments/routes.rs:4056) then converts the quoted amount into the payment's currency and requires an exact match. A USD→BTC quote almost never converts to the invoice's exact sats. A BTC→USD quote of the invoice amount matches trivially.
Fix: "To receive into a USD wallet, quote the USD amount: --unit usd --to btc. To pay from one, quote the invoice's BTC amount: --unit sats --to usd." Add the send direction to the payments guide next to the receive example at docs/guides/payments.md:34.
2. High: the sub-cent claim follows from the wrong quote direction and is unverified (docs/guides/payments.md:46, docs/guides/troubleshooting.md:106, src/api.rs:403)
"A quote in cents cannot match an invoice worth less than one cent" comes from issue #38's reproduction, which used --amount 1 --unit cents --to btc. The documented send quote is --amount N --unit sats --to usd, whose amount is the invoice's own sats, so the exact-match check passes. It is unknown whether the backend accepts a sats→usd quote that rounds to 0 cents or debits it correctly. Until that is checked on staging, telling users that a USD wallet can never pay a sub-cent invoice is a guess presented as a rule. "Exactly the payment's amount" is also only practically reachable with the BTC→USD direction, which none of the new text names.
Fix: In these three places, say to quote the invoice's sats with --to usd. Drop the sub-cent sentence, or verify it on staging with a 1 sat invoice and a --unit sats --to usd quote first. Then decide whether #38 is closed or only referenced.
3. Medium: the invalid_currency hint assumes a quote mismatch, but the API uses that type for at least 6 other causes (src/api.rs:357, src/api.rs:402)
The doc comment says "among other mismatches", but the hint always says the quote must match the amount. The backend also returns invalid_currency for:
- a USD amount on a Bolt11, on-chain or BIP21 send (
payments/routes.rs:3777,:3824,:3897) - a USD receive on a BTC line (
:3689) - a quote that does not involve the line's currency or the payment's currency (
:4029,:4047) - a non-asset currency for taproot (
:3736)
In each of these, "check what the quote converts to" sends the user the wrong way.
Fix: Either match only the mismatch detail (it starts with quoted amount / cannot validate quoted amount) and give no hint otherwise, or word the hint so it holds for every cause. Add a test with one non-quote invalid_currency detail, for example "USD payment currency must be on a USD line of credit".
4. Low: the local USD check's hint contradicts itself for a BTC wallet (src/input.rs:672)
is_usd() fires on currency: usd or a USD amount, not on the wallet's line. With --currency usd on a BTC-line wallet, the CLI says "needs --quote", and QUOTE_HINT then says "A wallet on a BTC line of credit needs no quote." The backend rejects that receive anyway with invalid_currency (payments/routes.rs:3689), so a quote does not help.
Fix: Append to this hint only: "On a BTC line of credit, use a BTC amount and drop --currency usd." Or use a local hint that says the same.
5. Nit: "The line of credit must be in one of the two" is always true (src/cli.rs:488, docs/guides/payments.md:44)
Currency here is only btc or usd. The backend rule (quoting/routes.rs:613) that matters is that the line's currency equals --to or the --unit currency. Say "in the line of credit's currency" once, or drop the clause.
Tests for new paths: same-currency rejection (both pairs), the invalid_currency hint, and the local quote hint (integration) are all covered. Nothing is missing beyond the non-quote invalid_currency case in finding 3. QUOTE_HINT reuse is good.
Checks on c0692e3 (verified with git rev-parse HEAD in the worktree):
cargo fmt --check: passcargo clippy --locked --all-targets -- -D warnings: passcargo machete --with-metadata(innix develop): passcargo test --locked: pass, 108 unit and 91 integration (run by the CRAP script)scripts/check-coverage.py: pass- CRAP report: 0 of 517 measured functions at or above 20. Highest are
wait_for_paymentat 19.60 (unchanged) andcheck_payloadat 17.00 (100% covered).
Not run: a staging run of either quote direction for a sub-cent invoice (no credentials in this session), and CodeRabbit status.
thebrandonlucas
left a comment
There was a problem hiding this comment.
Cicero review of c0692e3 (base master b25c05d)
Finding 1: the invalid_currency hint gives quote advice for errors that have nothing to do with a quote
src/api.rs:403 (also docs/guides/troubleshooting.md:104-106, docs/reference/exit-codes.md:16, CHANGELOG.md:26)
hint() attaches the "a quote must convert to exactly the payment's amount" hint to every invalid_currency. In backend master (1630b77c) the API returns invalid_currency for many reasons. Only two of them are a quote/amount mismatch (crates/api/src/domain/payments/routes.rs:4059, :4068). The others:
routes.rs:3689: "USD payment currency must be on a USD line of credit"routes.rs:3777,:3824,:3897: "Only BTC is supported for Bolt11/OnChain/BIP21 payments"routes.rs:3784,:3855,:3904: "Only BTC is supported for max_fee in ..."routes.rs:685,:723,:755: internal storageline_of_credit/error.rs:160: line-of-credit create and update
Failure scenario: someone pays from a BTC-line wallet with a USD amount in --data, or passes --max-fee in cents. They get the API's real reason, followed by a hint telling them to check voltage quotes get QUOTE_ID and use a BTC line of credit. That is the wrong next step, and in the first case it is backwards. The doc comment at src/api.rs:357 already says "among other mismatches", but the code does not check which one it got.
Fix: give the hint only when the detail shows a quote mismatch. For example, use ApiErrorType::InvalidCurrency if self.detail.as_deref().is_some_and(|d| d.starts_with("quoted amount ") || d.starts_with("cannot validate quoted amount ")), and return None otherwise. Add a test asserting no hint for, say, "Only BTC is supported for max_fee in Bolt11 payments"; that early return is currently untested. Narrow the troubleshooting section, the exit-codes sentence and the CHANGELOG line to say "when the API says the quoted amount does not match".
Checked, no finding
quotes createsame-currency rejection (src/input.rs:542): this matches the backend rule inquoting/routes.rs:613(the line of credit must equal the source or the--tocurrency). Bothsats→btcandcents→usdare tested.--unit usd --to btcin the hint is a valid combination.- Local USD-without-quote check (
src/input.rs:670):QUOTE_HINTis reused and not duplicated. The new message and hint are asserted intests/cli_integration.rs:2666. - The sub-cent claim in
payments.md:46matchesvalidate_quote_amount, which needs an exact converted match.
Checks run on c0692e3
- Verification gate:
cargo fmt --check,cargo clippy --locked --all-targets -- -D warnings,cargo machete --with-metadata(run innix develop),cargo test --locked(108 unit, 91 integration) andscripts/check-coverage.pyall pass. The tree is clean afterwards. - CRAP report (
scripts/crap-report.sh): 0 functions at or above 20. The highest iswait_for_payment(src/api.rs:1119) at 19.60, the same as on master.check_payloadis at 17.00 with 100% coverage. - Skills:
voltage-pr-reviewand its Rust backend style checklist. The repoAGENTS.md"Before you request review" checks. - Not run: a staging check of the real
invalid_currencyandmismatch_currencyresponses. The backend claims above come from reading the source at1630b77c, not from a live request.
c0692e3 to
a149b80
Compare
thebrandonlucas
left a comment
There was a problem hiding this comment.
Cicero re-review of a149b80
My finding is fixed. invalid_currency gets the hint only for a quoted amount detail. contains is right because payments/error.rs:118 wraps the detail. Both quote details and two non-quote cases are tested. The docs and CHANGELOG are narrowed to match. I also checked the new quote directions against NewQuoteRequest (quoting/routes.rs:516-522), and they match. Correction to my first review: I called --unit usd --to btc a valid hint for paying. Aurelius was right that it is the wrong direction for a send.
Finding (low): one sentence still says to reuse the USD amount
docs/guides/troubleshooting.md:100 (the quote_required section, next to the text this PR adds) still says "pass --quote QUOTE_ID and the same USD amount to the payment". That is right for a receive. For a send, the payment keeps the invoice's BTC amount: the btc_to_usd_quote example says "Reuse the exact BTC amount in the payment request". A reader following it would get the invalid_currency this PR explains. Fix: "...and the same amount you quoted (the invoice's BTC amount to pay, the USD amount to receive)", or link to the new paragraph in payments.md.
Checks on a149b80
- Gate in
nix develop: fmt, clippy, machete, tests (108 unit, 91 integration) and coverage pass. The tree is clean afterwards. - CRAP: 0 of 517 measured functions at or above 20. The highest is
wait_for_paymentat 19.60. - Not run: a staging check of
invalid_currencyresponses.
a149b80 to
1fd4f30
Compare
thebrandonlucas
left a comment
There was a problem hiding this comment.
Re-review at 1fd4f30 (Aurelius). My 5 findings from c0692e3 are fixed. I checked the quote directions against backend 1630b77c (NewQuoteRequest, quoting/routes.rs:516) and the two quoted amount details (payments/routes.rs:4060, :4069).
Low: docs/guides/payments.md:46 says "To receive a USD amount, quote it with --unit cents --to btc, as above." The example above it (:38) uses --amount 10 --unit usd --to btc. Both units are valid, but "as above" points to a different flag. Fix: write --unit usd --to btc here, to match the example, or drop "as above".
Checks:
- Gate on
a149b80(nix develop): fmt, clippy, machete, tests (108 unit, 91 integration) and coverage pass. - CRAP on
a149b80: 0 functions at or above 20. The highest iswait_for_paymentat 19.60, thencheck_payloadandendpointat 17.00. 1fd4f30changes onlytroubleshooting.mdfroma149b80. The commit is signedG blu@blu.cx, with no trailers.- Not run: a staging check of
invalid_currency.
1fd4f30 to
63bbdb3
Compare
thebrandonlucas
left a comment
There was a problem hiding this comment.
Re-review at 63bbdb3 (Aurelius). My low finding is fixed: payments.md:46 now says --unit usd --to btc, which matches the example at :38. No findings remain. The only change since 1fd4f30 is that line. Signed G blu@blu.cx, with no trailers.
thebrandonlucas
left a comment
There was a problem hiding this comment.
Re-review at 63bbdb3. This is not an approval. My findings are fixed. The change since 1fd4f30 is docs only: payments.md:46 now uses --unit usd --to btc, which matches the example at :38. I did not rerun the gate on this head, because no src/ or tests/ changed.
tee8z
left a comment
There was a problem hiding this comment.
Reviewed at 63bbdb3. CI is green. I ran the CRAP report on this head: 0 of 517 functions at or above 20, highest wait_for_payment at 19.60. No blocking issues, only nits:
- The receive hints in
src/api.rs(QUOTE_MISMATCH_HINT) andsrc/input.rs(same-currency rejection) say--unit cents --to btc.docs/guides/payments.mdsays--unit usd --to btc, which matches its example. Both work, but readers compare them. Pick one, and update the hint assertion in theinput.rstests. docs/guides/payments.mdsays "The CLI rejects a quote whose--unitis already in the--tocurrency". That holds only on the flag path; a complete--databody still goes to the API and getsmismatch_currency. Consider "With--unitand--to, the CLI rejects…".ApiErrorBody::hintmatchesdetail.contains("quoted amount "). A short comment naming the backend message it depends on would help whoever changes either side next.- The description reports the checks on
a149b80. Say that only docs changed since then, or name the head.
tee8z
left a comment
There was a problem hiding this comment.
Approved 63bbdb3 against master. The quote directions, local rejections, and API error hints match the reviewed contract. Remaining hint-placement and wording suggestions are nonblocking.
Validation on this unchanged head: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked, and python3 scripts/check-coverage.py passed. CRAP: 517 measured functions, none at or above 20; highest wait_for_payment 19.60. 137 test functions were unmeasured.
A USD payment without a quote now names --quote QUOTE_ID, says it needs a wallet on a USD line of credit, and says how to create the quote, instead of naming the API field quote_id. quotes create rejects a --unit already in the --to currency before sending it, since the quote would convert nothing, and its hint names both directions: the invoice's sats to usd to pay, a USD amount to btc to receive. An invalid_currency rejection of a quoted amount says the quote must convert to exactly the payment's amount and how to quote it. Other invalid_currency rejections get no hint. Closes #39 Closes #35 Closes #38
63bbdb3 to
641003e
Compare
|
Addressed tee8z review
|
tee8z
left a comment
There was a problem hiding this comment.
Approved 641003e against master 905cf4e. This head is the rebase of 63bbdb3, plus fixes for my nits:
- One receive form,
--unit usd --to btc, inQUOTE_MISMATCH_HINT, in the same-currency rejection, in its test, and inpayments.md. payments.mdlimits the local rejection to--unitand--to, and says that a--databody goes to the API as is.- A comment on the
"quoted amount "match names both backend messages that it depends on. USD_PAYMENT_HINTis now a privateconstininput.rs.
Validation on this head, in nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (108 unit, 91 integration) and python3 scripts/check-coverage.py passed. CRAP: 517 measured functions, none at or above 20, highest wait_for_payment at 19.60; 137 functions were unmeasured. When I approved, CI on x86_64 macOS was still pending, and the other jobs had passed.
Stack 1 of 6. Base:
master. Next:feat/cli-next-step-after-accept.Closes #39, closes #35, closes #38.
Problem
--quotefailed withUSD payment flows require an explicit quote_id. The message named the API field instead of the flag and gave no next step.quotes createsends an amount unit that the line of credit rejects #35:quotes create --unit sats --to btcpassed locally, then the API rejected it withmismatch_currency. The help did not say which currency each flag names.invalid_currencyrejection of a quoted amount had no hint.Behavior
--quoteUSD payment flows require an explicit quote_idA USD payment needs --quote QUOTE_ID (quote_id in --data), with a hint: a USD payment needs a wallet on a USD line of credit and a quote (quotes create,quotes get,--quote); a BTC-line wallet uses--currency btcquotes create --unit sats --to btcmismatch_currency--unit sats is already in the --to currency, btc; …, with a hint naming both directions:--unit sats --to usdto pay a BTC invoice from a USD wallet,--unit usd --to btcto receive a USD amountinvalid_currencywhose detail names a quoted amountquotes get, and quote an invoice's BTC amount--unit sats --to usdor a USD amount--unit usd --to btcinvalid_currency, any other detailThe API returns
invalid_currencyfor several mismatches that are not about a quote (a USD payment on a BTC line, a non-BTC Bolt11 or on-chain amount,max_fee), so the hint is given only when the detail containsquoted amount. The backend wraps that detail asPayment currency must be valid: …, so the check iscontains, notstarts_with. The--amount,--unitand--tohelp says which currency each one names.payments.md,troubleshooting.md(newinvalid_currencyentry),exit-codes.mdand the changelog are updated.Backend rules checked (
backendorigin/master1630b77c):request_a_quoteaccepts a quote only when the line of credit's currency equals the source or the target (crates/api/src/domain/quoting/routes.rs:613). When the two sides are in the same currency, the quote converts nothing, so the CLI rejects it.NewQuoteRequestdocuments the directions (:516–:521): a USD wallet paying a known BTC amount quotes that BTC amountto: usd; a USD receive quotes the USD amountto: btc.validate_quote_amountneeds the quote to convert to exactly the payment amount (crates/api/src/domain/payments/routes.rs:4059,:4068). Otherinvalid_currencysources::3689,:3736,:3777,:3824,:3897,:4029,:4047.Limits
invalid_currencyhint is not confirmed on staging. Its text comes from the backend code.--unit sats --to usdthe quote is the invoice's own sats. The claim is removed, not verified.Checks
On head
641003e, rebased ontomaster905cf4e:cargo fmt --check,cargo clippy --locked --all-targets -- -D warnings,cargo machete --with-metadata,cargo test --locked(108 unit, 91 integration), andpython3 scripts/check-coverage.pypass. All ran innix develop.sats/btcandcents/usd, with its hint; the local USD quote check with its hint, as a unit test and through the CLI; theinvalid_currencyhint for both quoted-amount details, and no hint for a non-quoteinvalid_currencydetail or a missing detail.c66d2a3), which contains this commit: 0 of 555 measured functions at or above 20. The highest iswait_for_paymentat 19.60, unchanged from master. This head adds no branches since63bbdb3(it moves a constant and changes hint text), so it was not run separately.--unit usd --to btc, in the hint,payments.md, and the test;payments.mdscopes the local rejection to--unitand--to; a comment on the"quoted amount "match names the backend messages it depends on;USD_PAYMENT_HINTis a privateconstininput.rs.