From 237d3f30c035ee21a76d17086787a1a2b7796fe1 Mon Sep 17 00:00:00 2001 From: steven-tomlinson Date: Fri, 5 Dec 2025 11:01:54 -0800 Subject: [PATCH 1/6] fix: Update Lockb0x Sigil NFT description for clarity and correct typos --- index.html | 6 +++--- lockb0x/utils.js | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 1659166..83a6d91 100644 --- a/index.html +++ b/index.html @@ -183,9 +183,9 @@
although all entities encountered may not be.
What is a Lockb0x Sigil? @@ -81,7 +81,7 @@

Intel Unlocked

}); - + From f637cf9025b3679624fe716c7f572a3a6e4b34eb Mon Sep 17 00:00:00 2001 From: steven-tomlinson Date: Fri, 5 Dec 2025 14:03:33 -0800 Subject: [PATCH 3/6] feat: Enhance PoH logic with improved error handling and UI updates --- lockb0x/index.html | 48 +++++++++++++++++++++++++++++------ lockb0x/utils.js | 5 +++- poh-gate.html | 62 ++++++++++++++++++++++++---------------------- 3 files changed, 77 insertions(+), 38 deletions(-) diff --git a/lockb0x/index.html b/lockb0x/index.html index fbc7f35..4e3fb3f 100644 --- a/lockb0x/index.html +++ b/lockb0x/index.html @@ -109,22 +109,22 @@ + + + + - - - - - + From b2991aab46fe4ea5dcc1a09edb9cfca17e7775f9 Mon Sep 17 00:00:00 2001 From: steven-tomlinson Date: Fri, 5 Dec 2025 14:31:35 -0800 Subject: [PATCH 4/6] feat: Refactor token gating logic and network handling for improved wallet connection --- lockb0x/index.html | 89 +++++++++++++++-------------------------- lockb0x/utils.js | 62 +++++++++++++++++++---------- lockb0x/web3.js | 98 ++++++---------------------------------------- 3 files changed, 85 insertions(+), 164 deletions(-) diff --git a/lockb0x/index.html b/lockb0x/index.html index 4e3fb3f..06ad892 100644 --- a/lockb0x/index.html +++ b/lockb0x/index.html @@ -127,37 +127,13 @@ import { isMetaMaskInstalled, - isMetaMaskConnected, - getWalletConnected, - isWalletConnectionExpired, - isPohVerifiedForAddress, getProvider, - registerWalletEventHandlers + registerWalletEventHandlers, + getTokenGatingState, + connectWallet, + ensureLineaSepolia } from './utils.js'; - let pohStatus = false; - let walletAddress = null; - - // Utility: Ensure user is on Linea Sepolia, prompt switch if not - async function ensureLineaSepoliaNetwork() { - if (!window.ethereum) return true; - try { - const provider = getProvider(); - const network = await provider.getNetwork(); - if (network.chainId !== 59141n) { - await window.ethereum.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0xe704' }] // 59141 in hex - }); - return true; - } - return true; - } catch (err) { - // User rejected or MetaMask error - return false; - } - } - async function updateMintUI() { const mintBtn = document.getElementById('mintBtn'); const mintStatus = document.getElementById('mintStatus'); @@ -168,18 +144,11 @@ const connectBtn = document.getElementById('connectMetaMaskBtn'); // Check wallet and PoH status - pohStatus = false; - walletAddress = null; - let connected = false; - if (isMetaMaskInstalled()) { - connected = await isMetaMaskConnected(); - if (connected) { - walletAddress = getWalletConnected(); - if (walletAddress && !isWalletConnectionExpired()) { - pohStatus = await isPohVerifiedForAddress(walletAddress); - } - } - } + const gating = await getTokenGatingState(); + const connected = gating.connected; + const walletAddress = gating.wallet; + const pohStatus = gating.poh; + const gatingError = gating.error; // Show/hide Connect MetaMask button if (!isMetaMaskInstalled()) { @@ -245,11 +214,10 @@ web3Info.textContent = 'Connect MetaMask to mint.'; } else if (!onLinea) { web3Info.style.display = ''; - if (switchAttempted) { - web3Info.textContent = 'Failed to switch to Linea Sepolia. Please switch manually in MetaMask.'; - } else { - web3Info.textContent = 'Switch to Linea Sepolia network in MetaMask.'; - } + web3Info.textContent = 'Switch to Linea Sepolia network in MetaMask.'; + } else if (gatingError) { + web3Info.style.display = ''; + web3Info.textContent = gatingError; } else { web3Info.style.display = 'none'; web3Info.textContent = ''; @@ -261,10 +229,10 @@ // Connect MetaMask button logic document.getElementById('connectMetaMaskBtn').addEventListener('click', async (evt) => { evt.preventDefault(); - if (window.ethereum) { - try { - await window.ethereum.request({ method: 'eth_requestAccounts' }); - } catch (err) {} + try { + await connectWallet(); + } catch (err) { + console.error(err); } await updateMintUI(); }); @@ -283,19 +251,24 @@ const tierSelect = document.getElementById('tierSelect'); const tier = tierSelect ? tierSelect.value : 'standard'; // Check wallet and PoH again - let connected = isMetaMaskInstalled() && await isMetaMaskConnected(); - let provider, network, onLinea = false; - if (connected) { - try { - provider = getProvider(); - network = await provider.getNetwork(); - onLinea = (network.chainId === 59141n); - } catch {} - } + const gating = await getTokenGatingState(); + const connected = gating.connected; + const pohStatus = gating.poh; + if (!connected) { mintStatus.textContent = 'Please connect MetaMask.'; return; } + + let provider, network, onLinea = false; + try { + provider = await ensureLineaSepolia(); + network = await provider.getNetwork(); + onLinea = (network.chainId === 59141n); + } catch (err) { + mintStatus.textContent = err?.message || 'Please switch to Linea Sepolia network.'; + return; + } if (!onLinea) { mintStatus.textContent = 'Please switch to Linea Sepolia network.'; return; diff --git a/lockb0x/utils.js b/lockb0x/utils.js index f16674b..fd9203d 100644 --- a/lockb0x/utils.js +++ b/lockb0x/utils.js @@ -156,7 +156,9 @@ export async function getTokenGatingState() { connected = await isMetaMaskConnected(); wallet = getWalletConnected(); address = await getCurrentWalletAddress(); - poh = await isPohVerifiedForAddress(address); + // Prefer persisted wallet, but fall back to the live address if storage is empty + wallet = wallet || address; + poh = await isPohVerifiedForAddress(wallet); } catch (e) { error = e?.message || String(e); if (_globalGatingErrorHandler) _globalGatingErrorHandler(error); @@ -367,6 +369,39 @@ export function getProvider() { return _provider; } +// ------------------------------------------------------------- +// NETWORK HELPERS +// ------------------------------------------------------------- + +async function ensureNetwork(targetChainId, chainIdHex, friendlyName) { + if (!window.ethereum) throw new Error("MetaMask not available"); + + let provider = new window.ethers.BrowserProvider(window.ethereum); + const network = await provider.getNetwork(); + + if (network.chainId !== targetChainId) { + try { + await window.ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: chainIdHex }], + }); + provider = new window.ethers.BrowserProvider(window.ethereum); + } catch (err) { + throw new Error(`Please switch to the ${friendlyName} network.`); + } + } + + return provider; +} + +export function ensureLineaSepolia() { + return ensureNetwork(59141n, "0xE705", "Linea Sepolia"); +} + +export function ensureLineaMainnet() { + return ensureNetwork(59144n, "0xE708", "Linea Mainnet"); +} + // ------------------------------------------------------------- // SIGNER (NEVER REQUEST PERMISSIONS HERE) // ------------------------------------------------------------- @@ -457,28 +492,15 @@ export async function connectWallet() { if (!window.ethereum) { throw new Error("MetaMask is required."); } - // Request wallet connection - await window.ethereum.request({ method: "eth_requestAccounts" }); + const accounts = await window.ethereum.request({ method: "eth_requestAccounts" }); - // Create provider for Linea Sepolia, ENS disabled - const provider = new window.ethers.BrowserProvider(window.ethereum, { - chainId: 59141, - name: "linea-sepolia", - ensAddress: null - }); + // Ensure the wallet is on Linea Sepolia + const provider = await ensureLineaSepolia(); - // Check network; prompt switch if needed - const network = await provider.getNetwork(); - if (network.chainId !== 59141n) { - try { - await window.ethereum.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: "0xe704" }] // 59141 hex - }); - } catch (switchErr) { - throw new Error("Please switch to the Linea Sepolia network."); - } + // Persist the connected wallet for gating state + if (Array.isArray(accounts) && accounts.length > 0) { + setWalletConnected(accounts[0]); } // Return signer for connected wallet diff --git a/lockb0x/web3.js b/lockb0x/web3.js index a285844..7f005f2 100644 --- a/lockb0x/web3.js +++ b/lockb0x/web3.js @@ -1,86 +1,12 @@ -// Ensure wallet is on Linea Mainnet (chainId 59144) -export async function ensureLineaMainnet() { - if (!window.ethereum) throw new Error("MetaMask not available"); - let provider = new window.ethers.BrowserProvider(window.ethereum); - let network = await provider.getNetwork(); - if (network.chainId !== 59144n) { - try { - await window.ethereum.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: "0xE708" }], // 59144 hex - }); - provider = new window.ethers.BrowserProvider(window.ethereum); - } catch (err) { - throw new Error("Please switch to the Linea Mainnet network."); - } - } - return provider; -} -// web3.js - MetaMask, provider, network, and wallet event helpers - -export function isMetaMaskInstalled() { - return typeof window.ethereum !== 'undefined' && window.ethereum.isMetaMask; -} - -export async function isMetaMaskConnected() { - if (!isMetaMaskInstalled()) return false; - try { - const accounts = await window.ethereum.request({ method: 'eth_accounts' }); - return Array.isArray(accounts) && accounts.length > 0; - } catch { - return false; - } -} - -export async function getCurrentWalletAddress() { - if (!isMetaMaskInstalled()) return null; - try { - const accounts = await window.ethereum.request({ method: 'eth_accounts' }); - return Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null; - } catch { - return null; - } -} - -export function getProvider() { - if (!window.ethereum) throw new Error("MetaMask not available"); - return new window.ethers.BrowserProvider(window.ethereum); -} - -export async function getSigner() { - const provider = getProvider(); - const accounts = await provider.send("eth_accounts", []); - if (!accounts || accounts.length === 0) { - throw new Error("No connected wallet"); - } - return await provider.getSigner(); -} - -export async function ensureLineaSepolia() { - if (!window.ethereum) throw new Error("MetaMask not available"); - let provider = new window.ethers.BrowserProvider(window.ethereum); - let network = await provider.getNetwork(); - if (network.chainId !== 59141n) { - try { - await window.ethereum.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: "0xE705" }], // 59141 hex - }); - provider = new window.ethers.BrowserProvider(window.ethereum); - } catch (err) { - throw new Error("Please switch to the Linea Sepolia network."); - } - } - return provider; -} - -export function registerWalletEventHandlers(callback) { - if (window.ethereum) { - window.ethereum.on && window.ethereum.on('accountsChanged', () => { - if (typeof callback === 'function') callback(); - }); - window.ethereum.on && window.ethereum.on('chainChanged', () => { - if (typeof callback === 'function') callback(); - }); - } -} +// web3.js now delegates to utils.js to avoid duplicated logic. +// Re-export helpers so existing imports continue to work. +export { + isMetaMaskInstalled, + isMetaMaskConnected, + getCurrentWalletAddress, + getProvider, + getSigner, + ensureLineaSepolia, + ensureLineaMainnet, + registerWalletEventHandlers, +} from './utils.js'; From fa20e78edeb50f3b39be109bf11f3ced78995631 Mon Sep 17 00:00:00 2001 From: steven-tomlinson Date: Sun, 7 Dec 2025 17:15:50 -0800 Subject: [PATCH 5/6] refactor: Update contract and minting logic for improved wallet handling and error messages --- lockb0x/contract.js | 2 +- lockb0x/mint.js | 15 +++++--- lockb0x/session.js | 7 +--- lockb0x/utils.js | 83 ++++++++++++++++++++------------------------- lockb0x/web3.js | 12 ------- 5 files changed, 49 insertions(+), 70 deletions(-) delete mode 100644 lockb0x/web3.js diff --git a/lockb0x/contract.js b/lockb0x/contract.js index e1acaca..fcc7df3 100644 --- a/lockb0x/contract.js +++ b/lockb0x/contract.js @@ -30,7 +30,7 @@ export async function hasLockb0xSigilNFT(address) { if (chainId !== lineaSepoliaChainId) { return false; } - const provider = new ethers.providers.Web3Provider(window.ethereum); + const provider = new ethers.BrowserProvider(window.ethereum); const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, provider); try { const balance = await contract.balanceOf(address); diff --git a/lockb0x/mint.js b/lockb0x/mint.js index 87b614d..321d686 100644 --- a/lockb0x/mint.js +++ b/lockb0x/mint.js @@ -1,5 +1,5 @@ -// mint.js — FINAL CLEAN VERSION +// mint.js - NFT minting logic for Lockb0x // --------------------------------------------------------------------- import { connectWallet, @@ -11,11 +11,13 @@ import { registerWalletEventHandlers, setDebugMode, setGatingErrorHandler, - isWalletConnectionExpired, isPohVerifiedForAddress, getPohSignature, checkPohAndPersist } from "./utils.js"; + + + // Enable debug mode for development (set to false in production) setDebugMode(true); @@ -107,11 +109,14 @@ mintBtn.addEventListener("click", async () => { try { // Centralized gating check const { connected, wallet, error } = await getTokenGatingState(); - if (!connected || !wallet || isWalletConnectionExpired()) { + if (!connected || !wallet) { let msg = error ? `Mint blocked: ${error}` : "Mint blocked: Wallet connection required."; - if (isWalletConnectionExpired()) { - msg = "Mint blocked: Wallet connection expired. Please reconnect your wallet."; + if(!connected) { + msg = "Mint blocked: Wallet not connected."; + } else if (!wallet) { + msg = "Mint blocked: Wallet address not found."; } + mintStatus.textContent = msg; mintStatus.style.color = "#f66"; mintBtn.disabled = false; diff --git a/lockb0x/session.js b/lockb0x/session.js index cce577d..bd231aa 100644 --- a/lockb0x/session.js +++ b/lockb0x/session.js @@ -31,12 +31,7 @@ export const LOCAL_POH_KEY = (address) => `nodezero_poh_v1_${address.toLowerCase export async function checkPohAndPersist(address, pohVerifyFn) { let resolvedAddress = address; - if (!resolvedAddress) { - // Use web3.js for getCurrentWalletAddress if available - if (typeof window.getCurrentWalletAddress === 'function') { - resolvedAddress = await window.getCurrentWalletAddress(); - } - } + // If still no address, prompt user to connect wallet if (!resolvedAddress && window.ethereum) { try { diff --git a/lockb0x/utils.js b/lockb0x/utils.js index fd9203d..ec9bf60 100644 --- a/lockb0x/utils.js +++ b/lockb0x/utils.js @@ -1,3 +1,16 @@ +import { + getPohSignature, + setPohSignature, + isPohSignatureVerified, + getTokenGatingState, + setPohVerified, + isPohVerifiedForAddress, + checkPohAndPersist +} from './session.js'; + +const POH_API_BASE = (window.APP_CONFIG && window.APP_CONFIG.POH_API_BASE) ? window.APP_CONFIG.POH_API_BASE : 'https://poh-api.linea.build/poh/v2/'; + + /** * Centralized eligibility logic for minting. * @param {string} address - Wallet address (required) @@ -82,11 +95,12 @@ export async function checkOwnershipForAddress(address) { console.error("checkOwnershipForAddress: window.ethers missing"); return false; } - const provider = new window.ethers.providers.Web3Provider(window.ethereum); + const provider = new window.ethers.BrowserProvider(window.ethereum); const network = await provider.getNetwork(); + const chainId = typeof network.chainId === 'bigint' ? network.chainId : BigInt(network.chainId); // Only check on Linea Sepolia (59141) - if (network.chainId !== 59141) { - console.warn("checkOwnershipForAddress: Not on Linea Sepolia (59141)", { chainId: network.chainId }); + if (chainId !== 59141n) { + console.warn("checkOwnershipForAddress: Not on Linea Sepolia (59141)", { chainId }); return false; } const contract = new window.ethers.Contract(window.SIGIL_CONTRACT_ADDRESS, window.SIGIL_CONTRACT_ABI, provider); @@ -106,7 +120,7 @@ export async function checkOwnershipForAddress(address) { // These helpers store and retrieve the PoH signature for a given address in localStorage. // The signature is public and permanent for each address, and is reused for all future operations. -// Retrieve the PoH signature for the given address (returns string|null) +/* // Retrieve the PoH signature for the given address (returns string|null) export function getPohSignature(address) { if (!address) return null; const key = `poh_signature_${address.toLowerCase()}`; @@ -124,7 +138,7 @@ export function setPohSignature(address, signature) { // This is true if a signature exists in localStorage for the address. export function isPohSignatureVerified(address) { return !!getPohSignature(address); -} +} */ /** * Lockb0x Token-Gating Utilities * Centralizes all wallet/PoH state, event registration, debug mode, and error handling. @@ -150,14 +164,11 @@ function logDebug(...args) { * Returns the current token-gating state. * @returns {Promise<{connected: boolean, wallet: string|null, poh: boolean, error: string|null}>} */ -export async function getTokenGatingState() { +/* export async function getTokenGatingState() { let connected = false, wallet = null, address = null, poh = false, error = null; try { connected = await isMetaMaskConnected(); - wallet = getWalletConnected(); address = await getCurrentWalletAddress(); - // Prefer persisted wallet, but fall back to the live address if storage is empty - wallet = wallet || address; poh = await isPohVerifiedForAddress(wallet); } catch (e) { error = e?.message || String(e); @@ -165,7 +176,7 @@ export async function getTokenGatingState() { } logDebug('Gating state:', { connected, wallet, poh, error }); return { connected, wallet, poh, error }; -} +} export const LOCAL_WALLET_KEY = 'nodezero_wallet_connected_v2'; // v2: stores JSON with timestamp export const LOCAL_POH_KEY = (address) => `nodezero_poh_v1_${address.toLowerCase()}`; @@ -179,39 +190,10 @@ export function setWalletConnected(address) { }; localStorage.setItem(LOCAL_WALLET_KEY, JSON.stringify(data)); } - -// Get wallet connection state from localStorage (returns address if not expired, else null) -export function getWalletConnected() { - const data = localStorage.getItem(LOCAL_WALLET_KEY); - if (!data) return null; - try { - const parsed = JSON.parse(data); - // 24h = 86400000 ms - if (Date.now() - parsed.connectedAt > 86400000) { - localStorage.removeItem(LOCAL_WALLET_KEY); - return null; - } - return parsed.address; - } catch (e) { - localStorage.removeItem(LOCAL_WALLET_KEY); - return null; - } -} - -// Check if wallet connection is expired (returns true if expired, false if valid) -export function isWalletConnectionExpired() { - const data = localStorage.getItem(LOCAL_WALLET_KEY); - if (!data) return true; - try { - const parsed = JSON.parse(data); - return (Date.now() - parsed.connectedAt > 86400000); - } catch (e) { - return true; - } -} +*/ // Set PoH verified for address (permanent, never expires) -export function setPohVerified(address) { +/* export function setPohVerified(address) { if (address) { localStorage.setItem(LOCAL_POH_KEY(address), 'true'); } @@ -225,8 +207,7 @@ export function isPohVerifiedForAddress(address) { const value = localStorage.getItem(pohKey); return value === 'true'; } -const POH_API_BASE = (window.APP_CONFIG && window.APP_CONFIG.POH_API_BASE) ? window.APP_CONFIG.POH_API_BASE : 'https://poh-api.linea.build/poh/v2/'; - + */ // Check if MetaMask is installed export function isMetaMaskInstalled() { return typeof window.ethereum !== 'undefined' && window.ethereum.isMetaMask; @@ -289,7 +270,7 @@ export const TIER_PRICE = { * @param {function} pohVerifyFn - (Optional) A function to perform PoH verification and return the signature. * @returns {Promise<{ status: boolean, address: string|null, signature: string|null, error: string|null }>} */ -export async function checkPohAndPersist(address, pohVerifyFn) { +/* export async function checkPohAndPersist(address, pohVerifyFn) { let resolvedAddress = address; if (!resolvedAddress) { resolvedAddress = await getCurrentWalletAddress(); @@ -329,7 +310,7 @@ export async function checkPohAndPersist(address, pohVerifyFn) { } catch (err) { return { status: false, address: resolvedAddress, signature: null, error: err?.message || String(err) }; } -} +} */ // utils.js — Shared helpers for Lockb0x Symbol Designer & Mint // Uses ESM but expects ethers.min.js (UMD) to already be loaded globally. @@ -524,4 +505,14 @@ export function getTierPrice(tier) { default: throw new Error("Unknown tier: " + tier); } -} \ No newline at end of file +} + +export { + getPohSignature, + setPohSignature, + isPohSignatureVerified, + getTokenGatingState, + setPohVerified, + isPohVerifiedForAddress, + checkPohAndPersist + } from './session.js'; \ No newline at end of file diff --git a/lockb0x/web3.js b/lockb0x/web3.js deleted file mode 100644 index 7f005f2..0000000 --- a/lockb0x/web3.js +++ /dev/null @@ -1,12 +0,0 @@ -// web3.js now delegates to utils.js to avoid duplicated logic. -// Re-export helpers so existing imports continue to work. -export { - isMetaMaskInstalled, - isMetaMaskConnected, - getCurrentWalletAddress, - getProvider, - getSigner, - ensureLineaSepolia, - ensureLineaMainnet, - registerWalletEventHandlers, -} from './utils.js'; From 526e211d6d456f57104eec7904d94898f7435f5d Mon Sep 17 00:00:00 2001 From: steven-tomlinson Date: Thu, 11 Dec 2025 12:15:50 -0800 Subject: [PATCH 6/6] refactor: Simplify PoH verification logic by removing persistence and using on-demand API checks --- js/header-logic.js | 8 +- lockb0x/index.html | 2 +- lockb0x/mint-poh.js | 52 +++++++++++++ lockb0x/mint.js | 40 ++++------ lockb0x/session.js | 174 ++++++++++++++++++++++------------------- lockb0x/utils.js | 183 ++++++++++---------------------------------- poh-gate.html | 6 +- 7 files changed, 210 insertions(+), 255 deletions(-) create mode 100644 lockb0x/mint-poh.js diff --git a/js/header-logic.js b/js/header-logic.js index 568771f..6bbe898 100644 --- a/js/header-logic.js +++ b/js/header-logic.js @@ -102,9 +102,9 @@ async function updateCarrierIndicator() { if (isMetaMask && isConnected && account) { // Check PoH and NFT status using utils.js if available - if (window.lockb0xUtils && window.lockb0xUtils.isPohVerifiedForAddress) { + if (window.lockb0xUtils && window.lockb0xUtils.checkPohStatus) { try { - pohVerified = window.lockb0xUtils.isPohVerifiedForAddress(account); + pohVerified = await window.lockb0xUtils.checkPohStatus(account); } catch { pohVerified = false; } } if (window.lockb0xUtils && window.lockb0xUtils.checkOwnershipForAddress) { @@ -181,7 +181,7 @@ async function updateCarrierIndicator() { connectBtn._handlerSet = true; } // lockb0x utils unavailable - if (isConnected && (!window.lockb0xUtils || !window.lockb0xUtils.checkOwnershipForAddress || !window.lockb0xUtils.isPohVerifiedForAddress)) { + if (isConnected && (!window.lockb0xUtils || !window.lockb0xUtils.checkOwnershipForAddress || !window.lockb0xUtils.checkPohStatus)) { console.log('Advanced features unavailable (lockb0x not loaded).'); } } @@ -201,7 +201,7 @@ function waitForUtilsAndInit(retryCount) { isMetaMask = (typeof window.ethereum !== 'undefined' && window.ethereum.isMetaMask); - if (window.lockb0xUtils && window.lockb0xUtils.checkOwnershipForAddress && window.lockb0xUtils.isPohVerifiedForAddress) { + if (window.lockb0xUtils && window.lockb0xUtils.checkOwnershipForAddress && window.lockb0xUtils.checkPohStatus) { updateCarrierIndicator(); if (window.ethereum) { window.ethereum.on && window.ethereum.on('accountsChanged', updateCarrierIndicator); diff --git a/lockb0x/index.html b/lockb0x/index.html index 06ad892..4c69a4a 100644 --- a/lockb0x/index.html +++ b/lockb0x/index.html @@ -171,7 +171,7 @@ if (!onLinea) { // Attempt to switch network switchAttempted = true; - const switched = await ensureLineaSepoliaNetwork(); + await ensureLineaSepolia(); if (switched) { // Re-check network after switch network = await provider.getNetwork(); diff --git a/lockb0x/mint-poh.js b/lockb0x/mint-poh.js new file mode 100644 index 0000000..26872e8 --- /dev/null +++ b/lockb0x/mint-poh.js @@ -0,0 +1,52 @@ +import { getSigner, getContract, readParams } from "./utils.js"; +import { getPohSignatureFromAPI } from "./session.js"; + +/** + * Mint NFT using PoH free mint + * @param {string} address - Wallet address + * @param {string} tier - Minting tier + * @param {HTMLElement} statusElement - Element to display status messages + */ +export async function mintPoh(address, tier, statusElement) { + try { + if (statusElement) { + statusElement.textContent = "Requesting PoH signature…"; + statusElement.style.color = "#ccc"; + } + + const signer = await getSigner(); + const contract = await getContract(); + + // Fetch signature from PoH Signer API + const signatureHex = await getPohSignatureFromAPI(address); + if (!signatureHex) { + throw new Error("Failed to retrieve PoH signature. Please ensure you have completed Proof of Humanity verification."); + } + + // Convert hex string to bytes for contract + const signature = window.ethers.getBytes(signatureHex); + + const params = readParams(tier); + + if (statusElement) { + statusElement.textContent = "Submitting PoH transaction…"; + } + + const tx = await contract.mintPoHFree(params, signature); + const receipt = await tx.wait(); + + if (statusElement) { + statusElement.textContent = `PoH mint successful! Tx: ${receipt.hash}`; + statusElement.style.color = "#8f8"; + } + } + catch (err) { + console.error("PoH mint error:", err); + const errorMsg = err?.reason || err?.message || "PoH mint failed."; + if (statusElement) { + statusElement.textContent = errorMsg; + statusElement.style.color = "#f66"; + } + throw err; + } +} diff --git a/lockb0x/mint.js b/lockb0x/mint.js index 321d686..15e601e 100644 --- a/lockb0x/mint.js +++ b/lockb0x/mint.js @@ -11,10 +11,9 @@ import { registerWalletEventHandlers, setDebugMode, setGatingErrorHandler, - isPohVerifiedForAddress, - getPohSignature, - checkPohAndPersist + checkPohStatus } from "./utils.js"; +import { mintPoh } from "./mint-poh.js"; @@ -147,29 +146,24 @@ mintBtn.addEventListener("click", async () => { let tx; let codeHash = null; - if (tier === "standard" && isPohVerifiedForAddress(wallet)) { - let pohSignature = getPohSignature(wallet); - let pohPayload = localStorage.getItem(`poh_payload_${wallet.toLowerCase()}`); - // Validate PoH payload/signature - if (!pohSignature || !pohPayload) { - // Auto-trigger PoH verification - mintStatus.textContent = "Verifying Proof of Humanity..."; - const pohResult = await checkPohAndPersist(wallet); - if (!pohResult.status || !pohResult.signature) { - mintStatus.textContent = "PoH verification failed. Cannot mint for free."; - mintStatus.style.color = "#f66"; - return; - } - pohSignature = pohResult.signature; - pohPayload = localStorage.getItem(`poh_payload_${wallet.toLowerCase()}`); - if (!pohPayload) { - mintStatus.textContent = "PoH payload missing after verification."; - mintStatus.style.color = "#f66"; + // Check if user is PoH verified for free standard tier mint + if (tier === "standard") { + const isPoh = await checkPohStatus(wallet); + if (isPoh) { + // Use mintPoh function for free PoH mint + try { + await mintPoh(wallet, tier, mintStatus); + return; // mintPoh handles the transaction and status updates + } catch (err) { + // Error already handled in mintPoh, just return return; } } - tx = await contract.mintPoHFree(params, pohPayload, pohSignature); - } else if (tier === "standard") { + // Fall through to paid standard mint if not PoH verified + } + + // Paid minting paths (standard without PoH, VIP, Premium) + if (tier === "standard") { tx = await contract.mintStandard(params, { value: priceWei }); } else if (tier === "vip") { const code = document.getElementById("secretCode").value.trim(); diff --git a/lockb0x/session.js b/lockb0x/session.js index bd231aa..214f8b7 100644 --- a/lockb0x/session.js +++ b/lockb0x/session.js @@ -1,35 +1,59 @@ -// Returns true if PoH is verified for the currently connected wallet (localStorage only) -export async function isPohVerified() { +// session.js - Wallet connection, expiry, PoH on-demand checks, gating state, debug/error + +export const LOCAL_WALLET_KEY = 'nodezero_wallet_connected_v2'; + +// PoH API endpoints +const POH_API_BASE = (window.APP_CONFIG && window.APP_CONFIG.POH_API_BASE) ? window.APP_CONFIG.POH_API_BASE : 'https://poh-api.linea.build/poh/v2/'; +const POH_SIGNER_API_BASE = (window.APP_CONFIG && window.APP_CONFIG.POH_SIGNER_API_BASE) ? window.APP_CONFIG.POH_SIGNER_API_BASE : 'https://poh-signer-api.linea.build/poh/v2/'; + +/** + * Check PoH status on-demand via API (no persistence) + * @param {string} address - Wallet address to check + * @returns {Promise} True if address has PoH verification + */ +export async function checkPohStatus(address) { + if (!address) return false; try { - const wallet = getWalletConnected(); - if (!wallet) return false; - // Only check localStorage for PoH flag for this address - return isPohVerifiedForAddress(wallet); - } catch (e) { - if (_globalGatingErrorHandler) _globalGatingErrorHandler(e?.message || String(e)); + const resp = await fetch(`${POH_API_BASE}${address}`); + if (!resp.ok) return false; + const text = (await resp.text()).trim(); + return text === 'true'; + } catch (err) { + console.error('checkPohStatus error:', err); return false; } } -// session.js - Wallet connection, expiry, PoH signature, gating state, debug/error - -export const LOCAL_WALLET_KEY = 'nodezero_wallet_connected_v2'; -export const LOCAL_POH_KEY = (address) => `nodezero_poh_v1_${address.toLowerCase()}`; - - +/** + * Get PoH signature from PoH Signer API (no persistence) + * @param {string} address - Wallet address + * @returns {Promise} Hex signature string or null if not verified + */ +export async function getPohSignatureFromAPI(address) { + if (!address) return null; + try { + const resp = await fetch(`${POH_SIGNER_API_BASE}${address}`); + if (!resp.ok) { + if (resp.status === 404) { + return null; // No PoH verification for this address + } + throw new Error(`PoH Signer API error: ${resp.status}`); + } + const signature = (await resp.text()).trim(); + return signature || null; + } catch (err) { + console.error('getPohSignatureFromAPI error:', err); + return null; + } +} -// Check PoH for current wallet, persist if verified, returns {status, address, error} /** - * Check PoH and persist signature if not already present. + * Check PoH for current wallet (on-demand, no persistence) * If address is not provided, uses the currently connected wallet address (async). - * This restores backward compatibility with previous usage in index.html and other callers. - * * @param {string} [address] - The wallet address to check. If omitted, uses current wallet. - * @param {function} pohVerifyFn - (Optional) A function to perform PoH verification and return the signature. - * @returns {Promise<{ status: boolean, address: string|null, signature: string|null, error: string|null }>} + * @returns {Promise<{ status: boolean, address: string|null, error: string|null }>} */ - -export async function checkPohAndPersist(address, pohVerifyFn) { +export async function checkPoh(address) { let resolvedAddress = address; // If still no address, prompt user to connect wallet @@ -40,42 +64,40 @@ export async function checkPohAndPersist(address, pohVerifyFn) { resolvedAddress = accounts[0]; } } catch (e) { - return { status: false, address: null, signature: null, error: 'Wallet connection rejected.' }; + return { status: false, address: null, error: 'Wallet connection rejected.' }; } } if (!resolvedAddress) { - return { status: false, address: null, signature: null, error: 'No address provided.' }; + return { status: false, address: null, error: 'No address provided.' }; } - // 1. Check for existing PoH flag - if (isPohVerifiedForAddress(resolvedAddress)) { - return { status: true, address: resolvedAddress, signature: null, error: null }; - } - // 2. Perform PoH verification (default: API check, or custom function) - const POH_API_BASE = (window.APP_CONFIG && window.APP_CONFIG.POH_API_BASE) ? window.APP_CONFIG.POH_API_BASE : 'https://poh-api.linea.build/poh/v2/'; + + // Perform PoH verification via API try { - let verified = false; - if (typeof pohVerifyFn === 'function') { - verified = await pohVerifyFn(resolvedAddress); + const verified = await checkPohStatus(resolvedAddress); + if (verified) { + return { status: true, address: resolvedAddress, error: null }; } else { - // Default: check API - const resp = await fetch(`${POH_API_BASE}${resolvedAddress}`); - if (!resp.ok) { - return { status: false, address: resolvedAddress, signature: null, error: 'Unable to contact Linea PoH service. Please try again later.' }; - } - const text = (await resp.text()).trim(); - if (text === 'true') { - verified = true; - } else { - return { status: false, address: resolvedAddress, signature: null, error: 'No Proof of Humanity found for this wallet.' }; - } + return { status: false, address: resolvedAddress, error: 'No Proof of Humanity found for this wallet.' }; } - if (!verified) { - return { status: false, address: resolvedAddress, signature: null, error: 'PoH verification failed.' }; - } - setPohVerified(resolvedAddress); - return { status: true, address: resolvedAddress, signature: null, error: null }; } catch (err) { - return { status: false, address: resolvedAddress, signature: null, error: err?.message || String(err) }; + return { status: false, address: resolvedAddress, error: err?.message || String(err) }; + } +} + +// Backward compatibility alias +export const checkPohAndPersist = checkPoh; + +/** + * Returns true if PoH is verified for the currently connected wallet (on-demand check) + */ +export async function isPohVerified() { + try { + const wallet = getWalletConnected(); + if (!wallet) return false; + return await checkPohStatus(wallet); + } catch (e) { + if (_globalGatingErrorHandler) _globalGatingErrorHandler(e?.message || String(e)); + return false; } } @@ -115,32 +137,8 @@ export function isWalletConnectionExpired() { } } -export function setPohVerified(address) { - if (address) { - localStorage.setItem(LOCAL_POH_KEY(address), 'true'); - } -} - -export function isPohVerifiedForAddress(address) { - if (!address) return false; - return localStorage.getItem(LOCAL_POH_KEY(address)) === 'true'; -} - -export function getPohSignature(address) { - if (!address) return null; - const key = `poh_signature_${address.toLowerCase()}`; - return localStorage.getItem(key); -} - -export function setPohSignature(address, signature) { - if (!address || !signature) throw new Error('Address and signature required'); - const key = `poh_signature_${address.toLowerCase()}`; - localStorage.setItem(key, signature); -} - -export function isPohSignatureVerified(address) { - return !!getPohSignature(address); -} +// PoH persistence functions removed - now using on-demand API checks +// Removed: setPohVerified, isPohVerifiedForAddress, getPohSignature, setPohSignature, isPohSignatureVerified let _debugMode = false; export function setDebugMode(enabled) { @@ -156,12 +154,30 @@ function logDebug(...args) { if (_debugMode) console.debug('[TokenGating]', ...args); } -export async function getTokenGatingState(isMetaMaskConnected, getWalletConnected, isPohVerified) { +/** + * Returns the current token-gating state (on-demand PoH check) + * @returns {Promise<{connected: boolean, wallet: string|null, poh: boolean, error: string|null}>} + */ +export async function getTokenGatingState() { let connected = false, wallet = null, poh = false, error = null; try { - connected = await isMetaMaskConnected(); + // Check MetaMask connection + if (typeof window.ethereum !== 'undefined' && window.ethereum.isMetaMask) { + try { + const accounts = await window.ethereum.request({ method: 'eth_accounts' }); + connected = Array.isArray(accounts) && accounts.length > 0; + } catch (e) { + connected = false; + } + } + + // Get wallet from localStorage (if available) wallet = getWalletConnected(); - poh = await isPohVerified(); + + // Check PoH status on-demand + if (wallet) { + poh = await checkPohStatus(wallet); + } } catch (e) { error = e?.message || String(e); if (_globalGatingErrorHandler) _globalGatingErrorHandler(error); diff --git a/lockb0x/utils.js b/lockb0x/utils.js index ec9bf60..98c511d 100644 --- a/lockb0x/utils.js +++ b/lockb0x/utils.js @@ -1,11 +1,9 @@ import { - getPohSignature, - setPohSignature, - isPohSignatureVerified, getTokenGatingState, - setPohVerified, - isPohVerifiedForAddress, - checkPohAndPersist + checkPohAndPersist, + checkPoh, + checkPohStatus, + getPohSignatureFromAPI } from './session.js'; const POH_API_BASE = (window.APP_CONFIG && window.APP_CONFIG.POH_API_BASE) ? window.APP_CONFIG.POH_API_BASE : 'https://poh-api.linea.build/poh/v2/'; @@ -34,8 +32,8 @@ export async function getMintEligibility(address, tier, hasSecretCode = false) { if (alreadyMinted) { return { eligible: false, reason: 'You have already minted a Lockb0x Sigil NFT.', free: false, priceWei: 0n }; } - // 2. PoH status - const poh = isPohVerifiedForAddress(address); + // 2. PoH status (on-demand check) + const poh = await checkPohStatus(address); // 3. Tier logic let free = false; let priceWei = 0n; @@ -91,11 +89,13 @@ export async function checkOwnershipForAddress(address) { }); return false; } - if (!window.ethers) { - console.error("checkOwnershipForAddress: window.ethers missing"); + if (!window.ethers && typeof ethers === 'undefined') { + console.error("checkOwnershipForAddress: ethers missing"); return false; } - const provider = new window.ethers.BrowserProvider(window.ethereum); + // Use global ethers if available, fallback to window.ethers + const ethersLib = typeof ethers !== 'undefined' ? ethers : window.ethers; + const provider = new ethersLib.BrowserProvider(window.ethereum); const network = await provider.getNetwork(); const chainId = typeof network.chainId === 'bigint' ? network.chainId : BigInt(network.chainId); // Only check on Linea Sepolia (59141) @@ -103,7 +103,7 @@ export async function checkOwnershipForAddress(address) { console.warn("checkOwnershipForAddress: Not on Linea Sepolia (59141)", { chainId }); return false; } - const contract = new window.ethers.Contract(window.SIGIL_CONTRACT_ADDRESS, window.SIGIL_CONTRACT_ABI, provider); + const contract = new ethersLib.Contract(window.SIGIL_CONTRACT_ADDRESS, window.SIGIL_CONTRACT_ABI, provider); if (typeof contract.balanceOf !== 'function') { console.error("checkOwnershipForAddress: contract.balanceOf is not a function"); return false; @@ -116,29 +116,7 @@ export async function checkOwnershipForAddress(address) { return false; } } -// --- PoH Signature Storage & Retrieval Helpers --- -// These helpers store and retrieve the PoH signature for a given address in localStorage. -// The signature is public and permanent for each address, and is reused for all future operations. - -/* // Retrieve the PoH signature for the given address (returns string|null) -export function getPohSignature(address) { - if (!address) return null; - const key = `poh_signature_${address.toLowerCase()}`; - return localStorage.getItem(key); -} - -// Set PoH signature for the given address (for testing or manual override) -export function setPohSignature(address, signature) { - if (!address || !signature) throw new Error('Address and signature required'); - const key = `poh_signature_${address.toLowerCase()}`; - localStorage.setItem(key, signature); -} - -// Check if PoH is verified for the given address (returns boolean) -// This is true if a signature exists in localStorage for the address. -export function isPohSignatureVerified(address) { - return !!getPohSignature(address); -} */ +// PoH signature helpers removed - now using on-demand API checks via session.js /** * Lockb0x Token-Gating Utilities * Centralizes all wallet/PoH state, event registration, debug mode, and error handling. @@ -160,54 +138,7 @@ export function setGatingErrorHandler(fn) { function logDebug(...args) { if (_debugMode) console.debug('[TokenGating]', ...args); } -/** - * Returns the current token-gating state. - * @returns {Promise<{connected: boolean, wallet: string|null, poh: boolean, error: string|null}>} - */ -/* export async function getTokenGatingState() { - let connected = false, wallet = null, address = null, poh = false, error = null; - try { - connected = await isMetaMaskConnected(); - address = await getCurrentWalletAddress(); - poh = await isPohVerifiedForAddress(wallet); - } catch (e) { - error = e?.message || String(e); - if (_globalGatingErrorHandler) _globalGatingErrorHandler(error); - } - logDebug('Gating state:', { connected, wallet, poh, error }); - return { connected, wallet, poh, error }; -} -export const LOCAL_WALLET_KEY = 'nodezero_wallet_connected_v2'; // v2: stores JSON with timestamp -export const LOCAL_POH_KEY = (address) => `nodezero_poh_v1_${address.toLowerCase()}`; - - -// Set wallet connection state in localStorage (lowercase, with timestamp, 24h expiry) -export function setWalletConnected(address) { - if (!address) return; - const data = { - address: address.toLowerCase(), - connectedAt: Date.now() - }; - localStorage.setItem(LOCAL_WALLET_KEY, JSON.stringify(data)); -} -*/ - -// Set PoH verified for address (permanent, never expires) -/* export function setPohVerified(address) { - if (address) { - localStorage.setItem(LOCAL_POH_KEY(address), 'true'); - } -} - -// Check PoH verified for address (permanent) -export function isPohVerifiedForAddress(address) { - if (!address) return false; - const normalized = address.toLowerCase(); - const pohKey = LOCAL_POH_KEY(normalized); - const value = localStorage.getItem(pohKey); - return value === 'true'; -} - */ +// getTokenGatingState is now exported from session.js - no local implementation needed // Check if MetaMask is installed export function isMetaMaskInstalled() { return typeof window.ethereum !== 'undefined' && window.ethereum.isMetaMask; @@ -260,57 +191,7 @@ export const TIER_PRICE = { premium: window.ethers?.parseEther ? window.ethers.parseEther("0.05") : "0.05" }; -// Check PoH for current wallet, persist if verified, returns {status, address, error} -/** - * Check PoH and persist signature if not already present. - * If address is not provided, uses the currently connected wallet address (async). - * This restores backward compatibility with previous usage in index.html and other callers. - * - * @param {string} [address] - The wallet address to check. If omitted, uses current wallet. - * @param {function} pohVerifyFn - (Optional) A function to perform PoH verification and return the signature. - * @returns {Promise<{ status: boolean, address: string|null, signature: string|null, error: string|null }>} - */ -/* export async function checkPohAndPersist(address, pohVerifyFn) { - let resolvedAddress = address; - if (!resolvedAddress) { - resolvedAddress = await getCurrentWalletAddress(); - } - // If still no address, prompt user to connect wallet - if (!resolvedAddress && window.ethereum) { - try { - const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); - if (Array.isArray(accounts) && accounts.length > 0) { - resolvedAddress = accounts[0]; - } - } catch (e) { - return { status: false, address: null, signature: null, error: 'Wallet connection rejected.' }; - } - } - if (!resolvedAddress) { - return { status: false, address: null, signature: null, error: 'No address provided.' }; - } - // 1. Check for existing PoH flag - if (isPohVerifiedForAddress(resolvedAddress)) { - return { status: true, address: resolvedAddress, signature: null, error: null }; - } - // 2. Perform PoH verification (default: API check, or custom function) - try { - // Default: check API - const resp = await fetch(`${POH_API_BASE}${resolvedAddress}`); - if (!resp.ok) { - return { status: false, address: resolvedAddress, signature: null, error: 'Unable to contact Linea PoH service. Please try again later.' }; - } - const text = (await resp.text()).trim(); - if (text === 'true') { - setPohVerified(resolvedAddress); - return { status: true, address: resolvedAddress, signature: null, error: null }; - } else { - return { status: false, address: resolvedAddress, signature: null, error: 'No Proof of Humanity found for this account.' }; - } - } catch (err) { - return { status: false, address: resolvedAddress, signature: null, error: err?.message || String(err) }; - } -} */ +// checkPohAndPersist is now exported from session.js - no local implementation needed // utils.js — Shared helpers for Lockb0x Symbol Designer & Mint // Uses ESM but expects ethers.min.js (UMD) to already be loaded globally. @@ -345,8 +226,14 @@ export function getProvider() { if (!window.ethereum) throw new Error("MetaMask not available"); + // Use global ethers if available, fallback to window.ethers + const ethersLib = typeof ethers !== 'undefined' ? ethers : window.ethers; + if (!ethersLib || !ethersLib.BrowserProvider) { + throw new Error("ethers.js BrowserProvider not available"); + } + // Create provider exactly once - _provider = new window.ethers.BrowserProvider(window.ethereum); + _provider = new ethersLib.BrowserProvider(window.ethereum); return _provider; } @@ -357,7 +244,13 @@ export function getProvider() { async function ensureNetwork(targetChainId, chainIdHex, friendlyName) { if (!window.ethereum) throw new Error("MetaMask not available"); - let provider = new window.ethers.BrowserProvider(window.ethereum); + // Use global ethers if available, fallback to window.ethers + const ethersLib = typeof ethers !== 'undefined' ? ethers : window.ethers; + if (!ethersLib || !ethersLib.BrowserProvider) { + throw new Error("ethers.js BrowserProvider not available"); + } + + let provider = new ethersLib.BrowserProvider(window.ethereum); const network = await provider.getNetwork(); if (network.chainId !== targetChainId) { @@ -366,7 +259,7 @@ async function ensureNetwork(targetChainId, chainIdHex, friendlyName) { method: "wallet_switchEthereumChain", params: [{ chainId: chainIdHex }], }); - provider = new window.ethers.BrowserProvider(window.ethereum); + provider = new ethersLib.BrowserProvider(window.ethereum); } catch (err) { throw new Error(`Please switch to the ${friendlyName} network.`); } @@ -508,11 +401,13 @@ export function getTierPrice(tier) { } export { - getPohSignature, - setPohSignature, - isPohSignatureVerified, getTokenGatingState, - setPohVerified, - isPohVerifiedForAddress, - checkPohAndPersist - } from './session.js'; \ No newline at end of file + checkPohAndPersist, + checkPoh, + checkPohStatus, + getPohSignatureFromAPI, + getWalletConnected, + setWalletConnected + } from './session.js'; + +// Note: checkOwnershipForAddress is already exported above (line 79) \ No newline at end of file diff --git a/poh-gate.html b/poh-gate.html index fbc9ade..a9e179c 100644 --- a/poh-gate.html +++ b/poh-gate.html @@ -176,14 +176,13 @@

Humanity's descendants thank you for your support.

setPohStatus('Checking Proof of Humanity...'); let result; try { - result = await lockb0xUtils.checkPohAndPersist(); + result = await lockb0xUtils.checkPoh(); } catch (err) { setPohStatus('PoH check error: ' + (err.message || err)); return; } if (result.status && result.address) { lockb0xUtils.setWalletConnected(result.address); - lockb0xUtils.setPohVerified(result.address); setPohStatus('Proof of Humanity verified.'); } else { setPohStatus(result.error || 'Proof of Humanity check failed.'); @@ -210,8 +209,7 @@

Humanity's descendants thank you for your support.

} let poh = false; if (address) { - poh = lockb0xUtils.isPohVerifiedForAddress(address); - if (poh) lockb0xUtils.setPohVerified(address); + poh = await lockb0xUtils.checkPohStatus(address); } if (connected && poh) { web3Intro.style.display = 'none';