Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
e75b9cd to
4192390
Compare
f7a5217 to
50a6948
Compare
50a6948 to
c9dc126
Compare
| expect(result).toBe(DEFAULT_ACCOUNT); | ||
| }); | ||
|
|
||
| test('returns default for zero-amount invoice (amountSat undefined)', () => { |
There was a problem hiding this comment.
why default for 0 amount?
There was a problem hiding this comment.
My thinking was that its not going to be guaranteed the matching account will have enough balance because we don't know how much the user will send.
The alternative is to pick the account with the matching description if that account has a balance greater than 0. Maybe that's what we should do and then in the case that the user wants to send more than their balance they can change accounts.
There was a problem hiding this comment.
yeah that sounds better to me
| RESEND_WELCOME_TEMPLATE_ID=51320094-3fc9-416f-9c4a-de4fff0fc5e2 | ||
| # Map of invoice description -> mint URL for smart source-account selection on bolt11 sends. | ||
| # Example: {"Minibits":"https://mint.minibits.cash/Bitcoin"} | ||
| VITE_MINT_DESCRIPTION_MAP= |
There was a problem hiding this comment.
the idea is that we use this for our gift card mints so that when user goes to pink owl coffee place and scans the invoice we automatically select pink owl account if it has enough balance to cover the cost?
if so, why don't we use the exiting VITE_GIFT_CARDS env var to handle this too?
There was a problem hiding this comment.
because this can be used for any mint, not just a gift card. It can work for offer mints which don't use VITE_GIFT_CARDS which populates the discover section. It could also work for public mints, but that's probably out of scope
Maybe we should refactor a bit though, one problem I've seen with how things are now is that we rely on VITE_GIFT_CARDS to show the gift card image, so if the gift card image is in our code but not in this env var then the image doesn't show. Also, for gift cards we can show the disclaimer under the card like "This gift card is an alpha that expires on May 1st, 2026" and that's something that would be nice for offers too
There was a problem hiding this comment.
yeah doesn't seem like something we will need for non gift-card/offer mints anytime soon. especially since mints like minibits don't even have any description set
| return { success: false, error: result.error }; | ||
| } | ||
|
|
||
| const smartSelected = selectSourceAccountForBolt11({ |
There was a problem hiding this comment.
should we also do smart selection when initial account is selected?
also, I wonder if this might be annoying if user selects account to send from and then picks the destination because in that case this could change user's selection
There was a problem hiding this comment.
should we also do smart selection when initial account is selected?
Do you mean if accountId is set in the queryParams? Probably not, I would need to check why that gets set.
also, I wonder if this might be annoying if user selects account to send from and then picks the destination because in that case this could change user's selection
Hmm yea. I'm not sure exactly. I think it would be, but there's also a case to be made that it should pick the destination because why not if you have sufficient balance on your gift card.
Here's a breakdown of when the account is selected and overridden as is.
There was a problem hiding this comment.
Hmm yea. I'm not sure exactly. I think it would be, but there's also a case to be made that it should pick the destination because why not if you have sufficient balance on your gift card.
I guess if user doesn't want to spend gift card for this current purchases. Maybe they are saving rewards to then later spend for some bigger purchase if that makes any sense?
There was a problem hiding this comment.
does it make sense to use smart picked over one provided in the url when both hash and accountId are provided in url? I guess the question is when does the url query param gets set
| export const MINT_DESCRIPTION_MAP: MintDescriptionMap = parseMap(); | ||
|
|
||
| type SmartSelectionInput = { | ||
| decoded: DecodedBolt11; |
There was a problem hiding this comment.
i would call this just detination
There was a problem hiding this comment.
I went for decodedDestination
There was a problem hiding this comment.
i am not sure I like the name smart selection
There was a problem hiding this comment.
changed to pick-account-for-destintation
| if (!mintUrl) return defaultAccount; | ||
|
|
||
| const candidates = accounts.filter( | ||
| (a) => a.mintUrl === mintUrl && a.currency === 'BTC', |
There was a problem hiding this comment.
why hard code the currency here?
There was a problem hiding this comment.
because I didn't want to deal with exchange rate and gift card mints are only BTC right now, but we could make it work with USD.
If we do, then we need to have a heuristic to pick the account tif there's multiple currency accounts for the same mint. I'd probably just go with selecting the first account with sufficient balance because there's no way to know from which unit the invoice was generated.
| ); | ||
| if (candidates.length === 0) return defaultAccount; | ||
|
|
||
| if (decoded.amountSat === undefined) return defaultAccount; |
- revert(receive): use extractCashuToken directly in paste/scan handlers
classifyInput is semantically a send-destination classifier and receive
only handles cashu tokens, so the indirection added no value.
- refactor(scan): replace isSendInput/isReceiveInput type guards with a
direction field on ClassifiedInput; classifyInput returns null instead
of {type:'unknown'} for unrecognized input.
- refactor(send): pre-fill validation routes through selectDestination so
it gets the same account-aware allowZeroAmount logic as manual entry,
and so the store stays the single validation site. Fixes a latent bug
where Cashu accounts + zero-amount BOLT11 invoices passed pre-fill but
failed at quote creation with "Cashu accounts do not support amountless
lightning invoices".
- refactor(send): clientLoader is now a raw passthrough — reads hash,
clears URL, returns string|null. SendProvider orchestrates the
selectDestination init and toasts on failure.
- rename(send): destination-validators.ts -> validation.ts.
Addresses review feedback on #971.
…coded Per review feedback. The function returns the bech32-encoded invoice string (with lightning: prefix stripped); 'encoded' describes that more precisely and avoids shadowing the input parameter name.
The validator's mixed-case rejection isn't reachable from any UI entry point — send-input.tsx and the QR scanner both lowercase before calling the validator — so the normalization in 4192390 didn't fix any observable bug. Reverting to keep the diff minimal.
Move the lightning: prefix strip and lowercase normalization into decodeBolt11 — bech32 requires uniform case and the lightning: prefix is case-insensitive per BIP21. The cleaned bech32 string is returned alongside the decoded fields. parseBolt11Invoice destructures the result so callers read result.encoded for the canonical form and result.decoded for the extracted fields, with no nested 'decoded.encoded' awkwardness. Also lowercase before lightning-address validation in classify-input, since the validator's local-part regex only accepts lowercase.
- Tighten initialDestination prop doc comment - Remove smart-source-selection design spec (deferred to follow-up PR)
When SendProvider initialized the store via selectDestination on a hash destination (QR scan path), the invoice's amount was returned in the result but never written to the store, so the input field initialized to 0. Set it on the store so useMoneyInput's mount-time read picks it up. Skip when the invoice has no amount to preserve user-typed values for zero-amount invoices.
Per review feedback, callers can now take the decoded object directly instead of destructuring around an additional `encoded` property.
The previous comment described the write-side (scan page setting hash before navigation), not the read-side (clearing hash after consuming it). Per review feedback.
Preserves a user-typed amount when a destination is selected, instead of overriding it with the invoice amount.
…provider selectDestination no longer touches the store's amount field. The scanner-driven path (SendProvider mount) now sets amount explicitly after resolution, since SendInput will read from the store on its initial render. The manual paste path doesn't need a store update — SendInput is already mounted and updates its input from the return value directly.
31c34ae to
5c6c455
Compare
The scan-region overlay uses a 9999px box-shadow as a dimming vignette. On mobile the scanner section is fixed-fullscreen so the shadow has nowhere to escape, but on desktop it's a 400x400 relative box and the shadow leaks across the full viewport, dimming the page header text and the paste button. Adding sm:overflow-hidden clips it.
c9dc126 to
01de2dc
Compare
01de2dc to
654e38b
Compare
0ec3717 to
bc900a6
Compare
When a scanned/pasted BOLT11's description matches a configured mint URL, preselect the user's cashu account at that mint as the send source if the balance covers the invoice. Falls back to the user's default (loader path) or current account (manual paste) otherwise. Configuration via VITE_MINT_DESCRIPTION_MAP (description → mint URL JSON map), validated at build time. Selector is a pure function; USD candidates use the BTC-USD exchange rate for balance comparison and are skipped if rate is unavailable. Zero-amount invoices and unmatched descriptions silently fall through to the existing default-account behavior.
bc900a6 to
a31c7f3
Compare
a60d146 to
ec9ea22
Compare
|
superseded by #1042 |
Adds validPaymentDestinations: { descriptions, nodePubkeys } to the
gift-card config schema and a findMatchingOfferOrGiftCardAccount helper
that uses it to preselect the right cashu account for an incoming
BOLT11 invoice. Priority: offer > gift-card > default.
Selection happens in the send route loader (no first-render flash) and
on every paste/scan via the send store. Both populated lists in
validPaymentDestinations are required checks (AND); an empty list means
the dimension is unconstrained; both empty means the mint is
unconfigured and never matches.
To support pubkey matching, decodeBolt11 now recovers the payee node
key from the invoice signature — the n tag is optional in BOLT11 and
rarely included. Recovery uses a manual 5→8 bit packer to produce the
canonical preimage, since @scure/base's strict bech32.fromWords rejects
the partial trailing byte that real-world invoices commonly have.
nodePubkeys are normalized to lowercase at zod parse time.
Supersedes #1025. Production VITE_GIFT_CARDS must add
validPaymentDestinations to every entry before this deploys, or vite
will throw at startup.
Stacks on #971 (universal QR scanner).
Summary
VITE_MINT_DESCRIPTION_MAP(description → mint URL JSON map), validated at build time.Design doc was landed in #971 and explains the full shape:
docs/superpowers/specs/2026-04-21-smart-source-account-selection-design.md.Test plan
?accountId=XURL override still wins over smart selection in loader path