feat(sasquatch-pro): enforce the paid tier; make AI Pro-only
- useSasquatchPro: derive Pro expiry from what was ACTUALLY paid (largest tier the bolt11 covers, run from the receipt time), instead of trusting the user-signed `pro-expiry` tag — a 7-day payment could previously self-assert a year. Provider-authored receipt + amount checks unchanged. - SasquatchGuide: AI answers are now Pro-only. Free users get the static FAQ, glossary, trip planner and local relay lookups (all no-cost), plus a nudge to Pro when a question would otherwise need AI. - AGENTS.md updated.
This commit is contained in:
parent
087f490acc
commit
670a33123d
2 changed files with 15 additions and 5 deletions
|
|
@ -961,7 +961,9 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
|||
let usedCVM = false;
|
||||
let liveTs = 0; // timestamp of the streaming bubble (0 = none)
|
||||
let streamSuppressed = false; // output looks like a JSON tool call — never render it
|
||||
try {
|
||||
// AI answers are a Pro feature. Free users skip this entirely and fall
|
||||
// through to the static FAQ, glossary and local relay lookups below.
|
||||
if (isPro) try {
|
||||
// The system prompt goes in a real `system` message rather than being
|
||||
// smuggled into a user turn — the model then treats the relay/derived
|
||||
// text below as lower-trust input, blunting prompt-injection via event
|
||||
|
|
@ -1340,7 +1342,10 @@ Instructions:
|
|||
const fallback =
|
||||
staticAnswer ??
|
||||
answerFromGlossary(question) ??
|
||||
`My AI brain's catching some air right now 🦧 — but I've still got you:\n\n• 🗺️ [Map](/) — spots, shops, events near you\n• 🧪 [The Lab](/lab) — coaches, filmers, editors\n• 📖 [Glossary](/glossary) — 150+ tricks & terms\n• 👥 [Crews](/crews) — find your people\n\nOr just tell me a **city** ("spots in Barcelona") or a **trick** ("what's a nollie") and I'll answer straight up. 🛹`;
|
||||
(!isPro
|
||||
? // Free tier: no AI. Nudge to Pro, but point at what still works.
|
||||
"That one needs AI reasoning — a **Sasquatch Pro** feature ⚡🦧\n\nI can still find **spots, events, coaches, crews, shops, tours and gear** straight from the relays — ask me e.g. \"spots in Barcelona\" — and the [Glossary](/glossary) covers 150+ tricks & terms. Tap **⚡ Go Pro** up top to unlock AI answers."
|
||||
: `My AI brain's catching some air right now 🦧 — but I've still got you:\n\n• 🗺️ [Map](/) — spots, shops, events near you\n• 🧪 [The Lab](/lab) — coaches, filmers, editors\n• 📖 [Glossary](/glossary) — 150+ tricks & terms\n• 👥 [Crews](/crews) — find your people\n\nOr just tell me a **city** ("spots in Barcelona") or a **trick** ("what's a nollie") and I'll answer straight up. 🛹`);
|
||||
appendMessage({ role: 'sasquatch', text: fallback, timestamp: Date.now() });
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { useCurrentUser } from '@/hooks/useCurrentUser';
|
|||
import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
|
||||
import { getZapProviderPubkey } from '@/lib/lightningAddress';
|
||||
import { getSatoshisFromBolt11 } from '@/lib/bolt11';
|
||||
import { PRO_LIGHTNING_ADDRESS, PRO_TIERS } from '@/lib/sasquatchPro';
|
||||
import { PRO_LIGHTNING_ADDRESS, PRO_TIERS, DAY_SECONDS } from '@/lib/sasquatchPro';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
const PLATFORM_PUBKEY = PLATFORM_OWNER_PUBKEYS[0];
|
||||
|
|
@ -103,8 +103,13 @@ function expiryFromZapReceipt(
|
|||
}
|
||||
if (paidSats < MIN_PRO_SATS) return null;
|
||||
|
||||
const exp = Number(tagVal(zapReq.tags, 'pro-expiry'));
|
||||
return Number.isFinite(exp) && exp > 0 ? exp : null;
|
||||
// Enforce the tier from what was ACTUALLY paid. Never trust the user-signed
|
||||
// `pro-expiry` tag — that let a 7-day payment self-assert a year. Pick the
|
||||
// largest tier the payment covers and run the pass from when the receipt was
|
||||
// issued (so paying later genuinely extends access).
|
||||
const tier = [...PRO_TIERS].sort((a, b) => b.sats - a.sats).find((t) => paidSats >= t.sats);
|
||||
if (!tier) return null;
|
||||
return receipt.created_at + tier.days * DAY_SECONDS;
|
||||
}
|
||||
|
||||
export function useSasquatchPro(): ProStatus {
|
||||
|
|
|
|||
Loading…
Reference in a new issue