From 51b57a4a2ff8b772de7a634729617dfa50e969ab Mon Sep 17 00:00:00 2001 From: JoaquinBN Date: Mon, 2 Feb 2026 12:46:59 +0100 Subject: [PATCH 1/2] Fix builder journey auto-completion and add DEBUG startup log Remove automatic builder journey completion that could trigger prematurely. The journey completion is now explicitly triggered by user action via a "Complete Builder Journey" button. Also removes duplicate completion logic from Profile page and adds proper error toast messages. Adds a startup log line to surface the DEBUG setting value in production. --- backend/tally/settings.py | 4 ++- frontend/src/routes/BuilderWelcome.svelte | 34 ++++++------------ frontend/src/routes/Profile.svelte | 43 ----------------------- 3 files changed, 13 insertions(+), 68 deletions(-) diff --git a/backend/tally/settings.py b/backend/tally/settings.py index b50ed15..a84d177 100644 --- a/backend/tally/settings.py +++ b/backend/tally/settings.py @@ -36,7 +36,9 @@ def get_required_env(key): SECRET_KEY = get_required_env('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = get_required_env('DEBUG').lower() == 'true' +_debug_raw = get_required_env('DEBUG') +DEBUG = _debug_raw.lower() == 'true' +print(f"[STARTUP] DEBUG={DEBUG} (raw: {repr(_debug_raw)})") ALLOWED_HOSTS = get_required_env('ALLOWED_HOSTS').split(',') diff --git a/frontend/src/routes/BuilderWelcome.svelte b/frontend/src/routes/BuilderWelcome.svelte index 1edba67..093f969 100644 --- a/frontend/src/routes/BuilderWelcome.svelte +++ b/frontend/src/routes/BuilderWelcome.svelte @@ -3,6 +3,7 @@ import { push } from 'svelte-spa-router'; import { authState } from '../lib/auth'; import { getCurrentUser, journeyAPI, usersAPI, githubAPI } from '../lib/api'; + import { showError } from '../lib/toastStore'; import { getValidatorBalance } from '../lib/blockchain'; import Icon from '../components/Icons.svelte'; import BuilderProgress from '../components/BuilderProgress.svelte'; @@ -46,16 +47,6 @@ let requirement4Met = $derived(testnetBalance > 0); let requirement5Met = $derived(hasDeployedContract); let allRequirementsMet = $derived(requirement1Met && requirement2Met && requirement3Met && requirement4Met && requirement5Met); - let hasCalledComplete = $state(false); - - // Auto-complete journey when all requirements are met - $effect(() => { - if (allRequirementsMet && !hasCalledComplete && $authState.isAuthenticated && !isCompletingJourney) { - hasCalledComplete = true; - // If we already know deployments exist, just complete immediately - completeBuilderJourney(); - } - }); onMount(async () => { await loadData(); @@ -116,8 +107,7 @@ await checkDeployments(); // If deployments detected and all requirements met, complete immediately - if (hasDeployedContract && requirement1Met && requirement2Met && !hasCalledComplete) { - hasCalledComplete = true; + if (hasDeployedContract && allRequirementsMet) { await completeBuilderJourney(); } // Show instructions if still no deployments after manual check @@ -203,33 +193,28 @@ } async function completeBuilderJourney() { - if (!$authState.isAuthenticated || !allRequirementsMet) { + if (!$authState.isAuthenticated || !allRequirementsMet || isCompletingJourney) { return; } - // Don't re-check deployments if we already know they exist - // Just proceed with completion isCompletingJourney = true; try { const response = await journeyAPI.completeBuilderJourney(); - // If successful, redirect to profile with success message if (response.status === 201) { - // New builder created sessionStorage.setItem('builderJourneySuccess', 'true'); push(`/participant/${$authState.address}`); } else if (response.status === 200) { - // Already a builder, just redirect push(`/participant/${$authState.address}`); } } catch (err) { - // If already has the contribution and Builder profile, redirect anyway if (err.response?.status === 200) { push(`/participant/${$authState.address}`); + } else if (err.response?.status === 400) { + showError(err.response?.data?.error || 'Some requirements are not yet met. Please check and try again.'); } else { - // Reset flag to allow retry - hasCalledComplete = false; + showError('Something went wrong. Please try again later.'); } } finally { isCompletingJourney = false; @@ -240,6 +225,7 @@ const debouncedRefreshBalance = debounce(refreshBalance, 500); const debouncedRefreshDeployments = debounce(refreshDeployments, 500); const debouncedCheckRepoStar = debounce(checkRepoStar, 500); + const debouncedCompleteJourney = debounce(completeBuilderJourney, 500); @@ -424,14 +410,14 @@ Completing your Builder Journey... - {:else if hasBuilderWelcome || (allRequirementsMet && hasCalledComplete)} + {:else if allRequirementsMet}
{/if} diff --git a/frontend/src/routes/Profile.svelte b/frontend/src/routes/Profile.svelte index e5651d4..f8dc3d3 100644 --- a/frontend/src/routes/Profile.svelte +++ b/frontend/src/routes/Profile.svelte @@ -52,7 +52,6 @@ let hasDeployedContract = $state(false); let isRefreshingBalance = $state(false); let isClaimingBuilderBadge = $state(false); - let hasCalledComplete = $state(false); let hasStarredRepo = $state(false); let repoToStar = $state('genlayerlabs/genlayer-project-boilerplate'); let isCheckingRepoStar = $state(false); @@ -70,19 +69,6 @@ $authState.address?.toLowerCase() === participant.address.toLowerCase() ); - // Derived states for builder requirements - let requirement1Met = $derived(participant?.has_builder_welcome || false); - let requirement2Met = $derived(testnetBalance > 0); - let allRequirementsMet = $derived(requirement1Met && requirement2Met); - - // Auto-complete journey when all requirements are met - $effect(() => { - if (allRequirementsMet && !hasCalledComplete && isOwnProfile && !participant?.builder) { - hasCalledComplete = true; - completeBuilderJourney(); - } - }); - // Determine participant type let participantType = $derived( !participant ? null : @@ -301,35 +287,6 @@ } } - async function completeBuilderJourney() { - if (!$authState.isAuthenticated || !allRequirementsMet) { - return; - } - - try { - const response = await journeyAPI.completeBuilderJourney(); - - // If successful, show success notification and reload data - if (response.status === 201 || response.status === 200) { - showSuccess('Congratulations! 🎉 You are now a GenLayer Builder! Your Builder profile has been created and you can start contributing to the ecosystem.'); - - // Reload participant data to get Builder profile - const updatedUser = await getCurrentUser(); - participant = updatedUser; - } - } catch (err) { - // If already has the contribution and Builder profile - if (err.response?.status === 200) { - // Still reload data - const updatedUser = await getCurrentUser(); - participant = updatedUser; - } else { - // Reset flag to allow retry - hasCalledComplete = false; - } - } - } - async function handleGitHubLinked(updatedUser) { // Update participant with the updated user info from GitHubLink component participant = updatedUser; From 0f8097eafb24c1b2cdee401ce8c345283933d016 Mon Sep 17 00:00:00 2001 From: JoaquinBN Date: Wed, 4 Feb 2026 18:39:16 +0100 Subject: [PATCH 2/2] Remove GenLayer deployment verification from builder journey Clicking "Open Studio" now immediately marks the deploy step as complete without making an API call to verify actual contract deployment. The completion button is moved into the BuilderProgress component so it appears consistently in both /welcome and /profile routes. - Remove checkDeployments/refreshDeployments and related state - Remove Studio instructions modal from BuilderWelcome - Remove deployment API methods from frontend api.js - Remove deployment check from backend complete_builder_journey - Add Complete Builder Journey button to BuilderProgress component - Enable journey completion from Profile page --- backend/users/views.py | 27 +--- .../src/components/BuilderProgress.svelte | 51 +++--- frontend/src/lib/api.js | 2 - frontend/src/routes/BuilderWelcome.svelte | 150 +----------------- frontend/src/routes/Profile.svelte | 46 +++--- 5 files changed, 64 insertions(+), 212 deletions(-) diff --git a/backend/users/views.py b/backend/users/views.py index a687505..0e0a8ef 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -443,8 +443,7 @@ def complete_builder_journey(self, request): Requirements: 1. Has at least one contribution (any type) 2. Has testnet balance > 0 - 3. Has deployed at least one contract on GenLayer - + Also creates Builder profile if it doesn't exist. """ from contributions.models import Contribution, ContributionType @@ -519,30 +518,6 @@ def complete_builder_journey(self, request): # If we can't check balance, we'll allow proceeding (fail open) pass - # Check requirement 3: Has deployed at least one contract - try: - genlayer_service = GenLayerDeploymentService() - - # Convert address to checksum format for GenLayer API - checksum_address = Web3.to_checksum_address(user.address) - - deployment_result = genlayer_service.get_user_deployments(checksum_address) - - if not deployment_result.get('has_deployments', False): - return Response( - {'error': 'You need to deploy at least one contract to complete the builder journey. Use GenLayer Studio to deploy your first contract.'}, - status=status.HTTP_400_BAD_REQUEST - ) - - logger.debug(f"Deployment check passed: {deployment_result.get('deployment_count', 0)} deployments") - - except Exception as e: - logger.error(f"Failed to check deployments: {str(e)}") - return Response( - {'error': 'Failed to verify contract deployments. Please try again later.'}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) - # All requirements met, create the BUILDER contribution and Builder profile atomically try: with transaction.atomic(): diff --git a/frontend/src/components/BuilderProgress.svelte b/frontend/src/components/BuilderProgress.svelte index 9af1050..ea5564e 100644 --- a/frontend/src/components/BuilderProgress.svelte +++ b/frontend/src/components/BuilderProgress.svelte @@ -21,12 +21,12 @@ isRefreshingBalance = false, onCheckRequirements = null, isCheckingRequirements = false, - onCheckDeployments = null, - isCheckingDeployments = false, onOpenStudio = null, onGitHubLinked = null, onCheckRepoStar = null, - isCheckingRepoStar = false + isCheckingRepoStar = false, + onCompleteJourney = null, + isCompletingJourney = false } = $props(); // Network states @@ -181,6 +181,11 @@ // Check if wallet has testnet balance let hasTestnetBalance = $derived(testnetBalance && testnetBalance > 0); + + // Core requirements that gate journey completion + let allCoreRequirementsMet = $derived( + hasBuilderWelcome && !!githubUsername && hasStarredRepo && hasTestnetBalance && hasDeployedContract + );
@@ -584,25 +589,6 @@
{#if showActions}
- {#if !hasDeployedContract && onCheckDeployments} - - {/if} {#if onOpenStudio}
+ + + {#if onCompleteJourney} +
+ +
+ {/if} {/if} \ No newline at end of file diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js index f13eb9b..755d03c 100644 --- a/frontend/src/lib/api.js +++ b/frontend/src/lib/api.js @@ -58,8 +58,6 @@ export const usersAPI = { getCurrentUser: () => api.get('/users/me/'), updateUserProfile: (data) => api.patch('/users/me/', data), getAccountBalance: () => api.get('/users/balance/'), - checkDeployments: () => api.get('/users/check_deployments/'), - getDeploymentStatus: () => api.get('/users/deployment_status/'), getActiveValidators: () => api.get('/users/validators/'), getReferrals: () => api.get('/users/referrals/'), getReferralPoints: () => api.get('/users/referral_points/'), diff --git a/frontend/src/routes/BuilderWelcome.svelte b/frontend/src/routes/BuilderWelcome.svelte index 093f969..cab7d08 100644 --- a/frontend/src/routes/BuilderWelcome.svelte +++ b/frontend/src/routes/BuilderWelcome.svelte @@ -2,10 +2,9 @@ import { onMount } from 'svelte'; import { push } from 'svelte-spa-router'; import { authState } from '../lib/auth'; - import { getCurrentUser, journeyAPI, usersAPI, githubAPI } from '../lib/api'; + import { getCurrentUser, journeyAPI, githubAPI } from '../lib/api'; import { showError } from '../lib/toastStore'; import { getValidatorBalance } from '../lib/blockchain'; - import Icon from '../components/Icons.svelte'; import BuilderProgress from '../components/BuilderProgress.svelte'; import GitHubLink from '../components/GitHubLink.svelte'; @@ -22,9 +21,6 @@ let error = $state(''); let loading = $state(true); let isRefreshingBalance = $state(false); - let isCheckingDeployments = $state(false); - let hasCheckedDeploymentsOnce = $state(false); - let showStudioInstructions = $state(false); let isCompletingJourney = $state(false); // Debounce utility to prevent button spam @@ -61,7 +57,6 @@ githubUsername = currentUser?.github_username || ''; // Removed all automatic checks - user must manually refresh each requirement // - checkTestnetBalance() - Removed: only check when user clicks refresh - // - checkDeployments() - Removed: only check when user clicks refresh // - checkRepoStar() - Removed: only check when user clicks refresh } } catch (err) { @@ -83,45 +78,9 @@ } - async function checkDeployments() { - // Show spinner on first check - if (!hasCheckedDeploymentsOnce) { - isCheckingDeployments = true; - } - - try { - const response = await usersAPI.getDeploymentStatus(); - hasDeployedContract = response.data.has_deployments || false; - } catch (err) { - hasDeployedContract = false; - } finally { - hasCheckedDeploymentsOnce = true; - isCheckingDeployments = false; - } - } - - async function refreshDeployments() { - // Manual refresh of deployment status - isCheckingDeployments = true; - try { - await checkDeployments(); - - // If deployments detected and all requirements met, complete immediately - if (hasDeployedContract && allRequirementsMet) { - await completeBuilderJourney(); - } - // Show instructions if still no deployments after manual check - else if (!hasDeployedContract && hasCheckedDeploymentsOnce) { - showStudioInstructions = true; - } - } finally { - isCheckingDeployments = false; - } - } - - function openStudioWithInstructions() { - // Show instructions popup (Studio will be opened from the popup button) - showStudioInstructions = true; + function openStudio() { + hasDeployedContract = true; + window.open('https://studio.genlayer.com', '_blank', 'noopener,noreferrer'); } async function refreshBalance() { @@ -156,11 +115,7 @@ } async function checkRequirements() { - // Check balance and deployments - await Promise.all([ - refreshBalance(), - checkDeployments() - ]); + await refreshBalance(); } async function claimBuilderWelcome() { @@ -223,7 +178,6 @@ // Create debounced versions AFTER all functions are defined (500ms delay) const debouncedRefreshBalance = debounce(refreshBalance, 500); - const debouncedRefreshDeployments = debounce(refreshDeployments, 500); const debouncedCheckRepoStar = debounce(checkRepoStar, 500); const debouncedCompleteJourney = debounce(completeBuilderJourney, 500); @@ -391,102 +345,14 @@ isRefreshingBalance={isRefreshingBalance} onCheckRequirements={checkRequirements} isCheckingRequirements={false} - onCheckDeployments={debouncedRefreshDeployments} - isCheckingDeployments={isCheckingDeployments} - onOpenStudio={openStudioWithInstructions} + onOpenStudio={openStudio} onGitHubLinked={handleGitHubLinked} onCheckRepoStar={debouncedCheckRepoStar} isCheckingRepoStar={isCheckingRepoStar} + onCompleteJourney={debouncedCompleteJourney} + isCompletingJourney={isCompletingJourney} /> - - {#if isCompletingJourney} -
-
- - - - - Completing your Builder Journey... -
-
- {:else if allRequirementsMet} -
- -
- {/if} - - - {#if showStudioInstructions} -
showStudioInstructions = false}> -
e.stopPropagation()}> -
-
-

- Connect Your Wallet in Studio -

- -
- -
-

- To deploy contracts on GenLayer Studio, you need to: -

- -
    -
  1. - Connect your wallet in GenLayer Studio using the same address: - {$authState.address} -
  2. -
  3. - Deploy your contract using the Studio interface -
  4. -
  5. - Return here and click refresh to verify your deployment -
  6. -
- -
-

- Important: Make sure to use the same wallet address in Studio that you're using here. -

-
-
- -
- - -
-
-
-
- {/if} \ No newline at end of file diff --git a/frontend/src/routes/Profile.svelte b/frontend/src/routes/Profile.svelte index f8dc3d3..2514925 100644 --- a/frontend/src/routes/Profile.svelte +++ b/frontend/src/routes/Profile.svelte @@ -15,7 +15,7 @@ import { authState } from '../lib/auth'; import { getValidatorBalance } from '../lib/blockchain'; import Avatar from '../components/Avatar.svelte'; - import { showSuccess, showWarning } from '../lib/toastStore'; + import { showSuccess, showWarning, showError } from '../lib/toastStore'; import { parseMarkdown } from '../lib/markdownLoader.js'; // Import route params from svelte-spa-router @@ -55,7 +55,7 @@ let hasStarredRepo = $state(false); let repoToStar = $state('genlayerlabs/genlayer-project-boilerplate'); let isCheckingRepoStar = $state(false); - let isCheckingDeployments = $state(false); + let isCompletingJourney = $state(false); let referralData = $state(null); let loadingReferrals = $state(false); let hasShownStatsErrorToast = $state(false); @@ -307,22 +307,34 @@ } } - async function checkDeployments() { - isCheckingDeployments = true; + function openStudio() { + hasDeployedContract = true; + window.open('https://studio.genlayer.com', '_blank', 'noopener,noreferrer'); + } + + async function completeBuilderJourney() { + if (!$authState.isAuthenticated || isCompletingJourney) return; + + isCompletingJourney = true; try { - const response = await usersAPI.getDeploymentStatus(); - hasDeployedContract = response.data.has_deployments || false; + const response = await journeyAPI.completeBuilderJourney(); + if (response.status === 201 || response.status === 200) { + // Reload participant data to reflect builder status + await fetchParticipantData($authState.address); + } } catch (err) { - hasDeployedContract = false; + if (err.response?.status === 200) { + await fetchParticipantData($authState.address); + } else if (err.response?.status === 400) { + showError(err.response?.data?.error || 'Some requirements are not yet met.'); + } else { + showError('Something went wrong. Please try again later.'); + } } finally { - isCheckingDeployments = false; + isCompletingJourney = false; } } - function openStudio() { - window.open('https://studio.genlayer.com', '_blank', 'noopener,noreferrer'); - } - async function fetchParticipantData(participantAddress) { try { loading = true; @@ -417,12 +429,6 @@ testnetBalance = 0; }); - // Check for contract deployments asynchronously - usersAPI.getDeploymentStatus().then(deploymentResult => { - hasDeployedContract = deploymentResult.data.has_deployments || false; - }).catch(err => { - hasDeployedContract = false; - }); } } } catch (err) { @@ -1304,9 +1310,9 @@ onGitHubLinked={handleGitHubLinked} onCheckRepoStar={checkRepoStar} isCheckingRepoStar={isCheckingRepoStar} - onCheckDeployments={checkDeployments} - isCheckingDeployments={isCheckingDeployments} onOpenStudio={openStudio} + onCompleteJourney={completeBuilderJourney} + isCompletingJourney={isCompletingJourney} /> {:else}