added nftcache functionality and cleaned up the settings page

This commit is contained in:
Siavash Sameni
2025-08-29 09:05:58 +04:00
parent 62653437b5
commit 4c67bf3a15
2 changed files with 350 additions and 197 deletions

View File

@@ -78,6 +78,23 @@ export default function DappPage() {
const { writeContractAsync, isPending: writePending } = useWriteContract();
const { sendTransactionAsync, isPending: txPending } = useSendTransaction();
// NFTCache settings (from localStorage; defaults to env or '/nftcache')
const [nftcacheEnabled, setNftcacheEnabled] = useState<boolean>(false);
const [nftcacheBaseUrl, setNftcacheBaseUrl] = useState<string>((process.env.NEXT_PUBLIC_NFTCACHE_URL || process.env.NFTCACHE_URL || '/nftcache').replace(/\/$/, ''));
const [nftcacheApiKey, setNftcacheApiKey] = useState<string>((process.env.NEXT_PUBLIC_NFTCACHE_API_KEY || process.env.NFTCACHE_API_KEY || '').trim());
useEffect(() => {
try {
if (typeof window !== 'undefined' && window.localStorage) {
const ls = window.localStorage;
setNftcacheEnabled(ls.getItem('nftcache:enabled') === '1');
const nurl = (ls.getItem('nftcache:baseUrl') || '').trim();
if (nurl) setNftcacheBaseUrl(nurl.replace(/\/$/, ''));
const nkey = (ls.getItem('nftcache:apiKey') || '').trim();
if (nkey) setNftcacheApiKey(nkey);
}
} catch {}
}, []);
// Notification settings (global) and per-position preferences
const [settingsOpen, setSettingsOpen] = useState(false);
@@ -203,6 +220,26 @@ export default function DappPage() {
return `nftScan:v1:${selectedChainId}:${nftAddress.toLowerCase()}`;
}, [nftAddress, selectedChainId]);
// Resolve network key used by NFTCache API
const nftcacheNetworkKey = useMemo(() => {
if (selectedChainId === base.id) return 'base';
if (selectedChainId === arbitrum.id) return 'arb';
if (selectedChainId === mainnet.id) return 'eth';
return String(selectedChainId);
}, [selectedChainId]);
// Map known contracts to nftcache slug keys
const nftcacheContractSlug = useMemo(() => {
const addr = (nftAddress || '').toLowerCase();
// base
if (addr === '0xcc9a350c5b1e1c9ecd23d376e6618cdfd6bbbdbe') return 'cbbtc';
if (addr === '0xab825f45e9e5d2459fb7a1527a8d0ca082c582f4') return 'weth';
// arbitrum
if (addr === '0xede6f5f8a9d6b90b1392dcc9e7fd8a5b0192bfe1') return 'wbbtc';
// fallback: return empty to use raw address
return '';
}, [nftAddress]);
const loadCache = (): ContractCache | null => {
try {
if (typeof window === 'undefined' || !cacheKey) return null;
@@ -242,6 +279,41 @@ export default function DappPage() {
}
}, [effectiveWallet, cacheKey]);
// Try NFTCache fetch first when enabled; fallback to local cache/scan
useEffect(() => {
const run = async () => {
if (!nftcacheEnabled) return; // disabled => keep current behavior
if (!effectiveWallet || !nftAddress) return;
try {
setScanBusy(true);
const contractParam = nftcacheContractSlug || nftAddress;
const url = `${nftcacheBaseUrl}/nfts?network=${encodeURIComponent(nftcacheNetworkKey)}&nft_contract=${encodeURIComponent(contractParam)}&user_wallet=${encodeURIComponent(effectiveWallet)}`;
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 12000);
const headers: Record<string, string> = { 'Accept': 'application/json' };
if (nftcacheApiKey) headers['X-API-Key'] = nftcacheApiKey;
const res = await fetch(url, { signal: ctrl.signal, headers });
clearTimeout(t);
if (!res.ok) throw new Error(`nftcache status ${res.status}`);
const data = await res.json();
let ids: string[] = [];
if (Array.isArray(data)) ids = data.map(String);
else if (data && Array.isArray((data as any).tokenIds)) ids = (data as any).tokenIds.map(String);
else if (data && Array.isArray((data as any).token_ids)) ids = (data as any).token_ids.map(String);
const bigs = ids.map((s) => BigInt(s)).filter((x, i, arr) => (arr.indexOf(x) === i));
setDetectedTokenIds(bigs);
setScanComplete(true);
console.log('[NFTCache] Loaded owner tokens', { count: bigs.length });
} catch (e) {
console.warn('[NFTCache] Fetch failed; falling back to local scan', e);
} finally {
setScanBusy(false);
}
};
run();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nftcacheEnabled, nftcacheBaseUrl, nftcacheApiKey, nftcacheNetworkKey, effectiveWallet, nftAddress, nftcacheContractSlug]);
// OwnerOf scan (batch of 12)
const scanMore = async () => {
if (!publicClient || !effectiveWallet || !nftAddress) return;
@@ -442,11 +514,16 @@ export default function DappPage() {
// Resolve tokenIds: either detected or manual entry
const tokenIds = useMemo(() => {
if (deeplinkTokenId.trim()) {
try { return [BigInt(deeplinkTokenId.trim())]; } catch { return []; }
// Show all sources together: detected, manual, and deep-linked (deduped)
const set = new Set<string>();
for (const id of detectedTokenIds) set.add(id.toString());
if (manualTokenId.trim()) {
try { set.add(BigInt(manualTokenId.trim()).toString()); } catch {}
}
const manual = manualTokenId.trim() ? [BigInt(manualTokenId.trim())] : [];
return [...detectedTokenIds, ...manual];
if (deeplinkTokenId.trim()) {
try { set.add(BigInt(deeplinkTokenId.trim()).toString()); } catch {}
}
return Array.from(set).map((s) => BigInt(s));
}, [detectedTokenIds, manualTokenId, deeplinkTokenId]);
// Build reads for debt contract