Files
dark-lord/contexts/WalletContext.tsx
drjones 2f928fbdc4 10x every page: real interactions, kill fake content, wire everything
- ChatWidget: remove illegal seeds, real localStorage per-handle chat,
  honest bot replies about market/forum/funds
- ForumBoard: wire to real forumState (loadForum/addThread/vote), kill
  fake stats and illegal seed posts
- Home page: privacy features list reflects reality, footer links real
- Links: kill all alert() calls, replace fake onions with real clearnet
  privacy resources + internal route grid
- Support: per-coin copied state, env-driven addresses, real BTC addr
- Inner circle: wire to AccountContext, tier system from LUX balance,
  remove hardcoded admin/shadow credentials and fake trading signals
- Drop box: real sealed-note localStorage system, honest about no
  anonymous upload capability, real file picker with receipt
- Messages: fully functional per-handle localStorage chat, AI-style
  contextual bot replies, clear history, honest about local storage
- Wallets: pivot from fake PayPal accounts to Digital Access Passes,
  wire Buy Now to cart via ShopProduct interface
- Testimonials: wire submit form to localStorage, interactive star
  rating 1-10, display submitted reviews above the fold
- Raffle: use real merchant BTC address, real per-handle entry storage,
  honest LUX-only prize disclaimer, fix 0x address
- Drops/Lotto: real number picker 1-49 with Quick Pick, ticket
  submission, match display against drawn numbers, demo disclaimer
- Sanctuary: real 4-4-6-2 breathing timer, meditation passage with
  timer, candle-lighting with localStorage notes
- Game: full playable Void Pong with canvas physics, CPU AI, scoring,
  rally counter, localStorage high score
- Security analysis: honest architecture breakdown with real grades,
  layer-by-layer analysis, practical OPSEC guide, fiction banner
- Trust: compute real scores from actual localStorage data (LUX,
  USD, forum posts, testimonials), FAQ accordion

Made-with: Cursor
2026-04-16 00:55:28 -07:00

188 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { createContext, useCallback, useContext, useEffect, useMemo, useState, ReactNode } from "react";
import type { VaultReceipt, VaultState } from "@/lib/metaGame";
import { addKey, addReceipt, defaultVaultState, loadVaultState, saveVaultState, setFlag } from "@/lib/metaGame";
import { getLedgerRow, migrateLegacyDeviceLedgerIfNeeded, setLedgerRow } from "@/lib/storeCreditStorage";
import { useAccount } from "@/contexts/AccountContext";
interface WalletContextType {
usdStoreCredit: number;
luxCredits: number;
spendLuxCredits: (amount: number) => boolean;
earnLuxCredits: (amount: number) => void;
spendUsdStoreCredit: (amount: number) => boolean;
verifyBtcDeposit: (txid: string) => Promise<{ ok: boolean; error?: string; creditedUsd?: number }>;
vault: VaultState;
collectKey: (key: string) => boolean;
addVaultReceipt: (receipt: VaultReceipt) => void;
setVaultFlag: (flag: string, value: boolean) => void;
}
const WalletContext = createContext<WalletContextType | undefined>(undefined);
export const useWallet = () => {
const context = useContext(WalletContext);
if (!context) {
throw new Error("useWallet must be used within WalletProvider");
}
return context;
};
interface WalletProviderProps {
children: ReactNode;
}
export const WalletProvider = ({ children }: WalletProviderProps) => {
const { user } = useAccount();
const handle = user?.username ?? null;
const [usdStoreCredit, setUsdStoreCredit] = useState(0);
const [luxCredits, setLuxCredits] = useState(0);
const [vault, setVault] = useState<VaultState>(() => defaultVaultState());
useEffect(() => {
if (!handle) {
setUsdStoreCredit(0);
setLuxCredits(0);
return;
}
migrateLegacyDeviceLedgerIfNeeded(handle);
const row = getLedgerRow(handle);
setUsdStoreCredit(row.usd);
setLuxCredits(row.lux);
}, [handle]);
useEffect(() => {
setVault(loadVaultState(handle));
}, [handle]);
useEffect(() => {
saveVaultState(handle, vault);
}, [vault, handle]);
const spendLuxCredits = useCallback((amount: number) => {
if (!handle) return false;
const safeAmount = Number.isFinite(amount) ? Math.max(0, Math.floor(amount)) : 0;
if (safeAmount <= 0) return true;
const row = getLedgerRow(handle);
if (row.lux < safeAmount) return false;
const nextLux = row.lux - safeAmount;
setLedgerRow(handle, { usd: row.usd, lux: nextLux, claimedTxids: row.claimedTxids });
setLuxCredits(nextLux);
return true;
}, [handle]);
const earnLuxCredits = useCallback(
(amount: number) => {
if (!handle) return;
const safeAmount = Number.isFinite(amount) ? Math.max(0, Math.floor(amount)) : 0;
if (safeAmount <= 0) return;
const row = getLedgerRow(handle);
const nextLux = row.lux + safeAmount;
setLedgerRow(handle, { usd: row.usd, lux: nextLux, claimedTxids: row.claimedTxids });
setLuxCredits(nextLux);
},
[handle],
);
const spendUsdStoreCredit = useCallback(
(amount: number) => {
if (!handle) return false;
const safe = Number.isFinite(amount) ? Math.max(0, Math.round(amount * 100) / 100) : 0;
if (safe <= 0) return true;
const row = getLedgerRow(handle);
if (row.usd + 1e-9 < safe) return false;
const nextUsd = Math.round((row.usd - safe) * 100) / 100;
setLedgerRow(handle, { usd: nextUsd, lux: row.lux, claimedTxids: row.claimedTxids });
setUsdStoreCredit(nextUsd);
return true;
},
[handle],
);
const verifyBtcDeposit = useCallback(
async (txid: string) => {
if (!handle) {
return { ok: false, error: "Sign in so deposits credit your handles balance." };
}
const normalized = String(txid || "").trim();
if (!/^[a-fA-F0-9]{64}$/.test(normalized)) {
return { ok: false, error: "Enter a valid 64-character transaction id" };
}
const row = getLedgerRow(handle);
if (row.claimedTxids.includes(normalized)) {
return { ok: false, error: "This transaction was already used to add credit" };
}
try {
const res = await fetch("/api/btc/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ txid: normalized }),
});
const data = (await res.json()) as {
ok?: boolean;
error?: string;
creditedUsd?: number;
};
if (!data.ok || typeof data.creditedUsd !== "number") {
return { ok: false, error: data.error || "Verification failed" };
}
const add = Math.round(data.creditedUsd * 100) / 100;
const nextUsd = Math.round((row.usd + add) * 100) / 100;
const claimed = [...row.claimedTxids, normalized];
setLedgerRow(handle, { usd: nextUsd, lux: row.lux, claimedTxids: claimed });
setUsdStoreCredit(nextUsd);
return { ok: true, creditedUsd: add };
} catch {
return { ok: false, error: "Network error while verifying" };
}
},
[handle],
);
const collectKey = (key: string) => {
const trimmed = String(key || "").trim();
if (!trimmed) return false;
if (vault.keys.includes(trimmed)) return false;
setVault((v) => addKey(v, trimmed));
return true;
};
const addVaultReceipt = (receipt: VaultReceipt) => {
setVault((v) => addReceipt(v, receipt));
};
const setVaultFlag = (flag: string, value: boolean) => {
const f = String(flag || "").trim();
if (!f) return;
setVault((v) => setFlag(v, f, Boolean(value)));
};
const value = useMemo(
() => ({
usdStoreCredit,
luxCredits,
spendLuxCredits,
earnLuxCredits,
spendUsdStoreCredit,
verifyBtcDeposit,
vault,
collectKey,
addVaultReceipt,
setVaultFlag,
}),
[
usdStoreCredit,
luxCredits,
spendLuxCredits,
earnLuxCredits,
spendUsdStoreCredit,
verifyBtcDeposit,
vault,
],
);
return <WalletContext.Provider value={value}>{children}</WalletContext.Provider>;
};