perf(access): keep the rate lookup off the unlock path, log entry timing
Tap → unlock took ~3 s on sintra. The card server's /session now fills fiat only from its warm rate cache (aiolabs/boltcards fix/session-fiat-from-cache); when it returns a currency with fiat null, the store prices the balance in that currency from the ATM's own rate source after the unlock, so the chip still shows the wallet's currency. Log how long the session call took and whether the server priced it, so the next latency question can be answered from the journal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
42e3657fe1
commit
8e11c41f62
2 changed files with 31 additions and 3 deletions
|
|
@ -343,6 +343,10 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
// The card balance is hidden by default on the public screen; the holder
|
||||
// reveals it with the eye toggle. Resets on re-lock.
|
||||
const cardBalanceRevealed = ref(false)
|
||||
// When the card server names a currency but didn't price the balance (its
|
||||
// rate cache was cold — it never blocks the unlock on a rate lookup), price
|
||||
// it here from the ATM's own rate source, in that currency.
|
||||
const cardFiatRate = ref<{ currency: string; btcPrice: number } | null>(null)
|
||||
const fiatCode = ref('USD')
|
||||
// Defaults are 0 — the operator's fee config (received via Nostr
|
||||
// kind-30078 `bitspire-fees:<atm_pubkey>` envelope from satmachineadmin)
|
||||
|
|
@ -488,6 +492,10 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
const card = loadedBoltCard.value
|
||||
if (!card) return null
|
||||
if (card.currency && card.fiat !== null) return { amount: card.fiat, currency: card.currency }
|
||||
const rate = cardFiatRate.value
|
||||
if (card.currency && rate && rate.currency === card.currency) {
|
||||
return { amount: (card.balanceSats / 1e8) * rate.btcPrice, currency: card.currency }
|
||||
}
|
||||
if (btcPrice.value && btcPrice.value > 0) {
|
||||
return { amount: (card.balanceSats / 1e8) * btcPrice.value, currency: fiatCode.value }
|
||||
}
|
||||
|
|
@ -556,6 +564,7 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
if (state === 'locked' && loadedBoltCard.value) {
|
||||
loadedBoltCard.value = null
|
||||
cardBalanceRevealed.value = false
|
||||
cardFiatRate.value = null
|
||||
}
|
||||
|
||||
// Detect network from first invoice we see
|
||||
|
|
@ -779,7 +788,14 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
boltCardProcessing.value = true
|
||||
nfcStatus.value = { state: 'processing', message: 'Verifying card…' }
|
||||
try {
|
||||
const t0 = Date.now()
|
||||
const opened = await window.electronAPI.openCardSession({ lnurlw })
|
||||
console.info(
|
||||
`[ATM] Card session ${opened.ok ? 'opened' : 'refused'} in ${Date.now() - t0} ms` +
|
||||
(opened.ok
|
||||
? ` (fiat ${opened.session.fiat === null ? 'not ' : ''}priced by card server)`
|
||||
: '')
|
||||
)
|
||||
if (!opened.ok) {
|
||||
nfcStatus.value = { state: 'declined', message: opened.reason }
|
||||
denyAccess(opened.reason)
|
||||
|
|
@ -793,8 +809,18 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
if (outcome.status === 'granted') {
|
||||
loadedBoltCard.value = opened.session
|
||||
cardBalanceRevealed.value = false
|
||||
cardFiatRate.value = null
|
||||
nfcStatus.value = { state: 'accepted', message: 'Card accepted' }
|
||||
grantAccess(outcome.role, outcome.credentialIdHash)
|
||||
// Price the balance in the card's currency off the unlock path.
|
||||
const { currency, fiat, externalId } = opened.session
|
||||
if (currency && fiat === null) {
|
||||
void fetchBtcPrice(currency).then((price) => {
|
||||
if (price && loadedBoltCard.value?.externalId === externalId) {
|
||||
cardFiatRate.value = { currency, btcPrice: price }
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// pin-required can't occur for card-only open-enrollment; treat as denied.
|
||||
const reason = outcome.status === 'denied' ? outcome.reason : 'card not authorized'
|
||||
|
|
|
|||
|
|
@ -63,9 +63,11 @@ one `hit` is recorded.
|
|||
- `balance_msat` — the card wallet's balance. Display only.
|
||||
- `currency` / `fiat` — the balance priced the way the LNbits wallet page does
|
||||
it: the wallet's own currency (per-wallet setting) first, else the instance's
|
||||
default accounting currency, at the server's rate. `null` when the server has
|
||||
no currency or the rate lookup failed; the ATM then prices the sats itself in
|
||||
its own fiat at its display rate. A rate failure never fails the session.
|
||||
default accounting currency. `fiat` is filled **only from the server's
|
||||
already-warm rate cache** — this response gates the unlock, and a cold rate
|
||||
lookup queries external exchanges (~1 s). On a cache miss it is `null` and
|
||||
the ATM prices the sats itself: in `currency` from its own rate source, else
|
||||
in its own fiat at its display rate. No rate lookup ever blocks the session.
|
||||
- `withdraw` — the LUD-03 second step. The ATM calls
|
||||
`callback?k1=<hit>&pr=<bolt11>` at cash-out Complete. `null` with
|
||||
`withdraw_blocked_reason` set when `/scan` would have refused (daily limit
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue