Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions templates/default/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"connectkit": "^1.9.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.28.0",
"viem": "2.30.6",
"wagmi": "^2.14.16"
},
Expand Down
12 changes: 8 additions & 4 deletions templates/default/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
// or FITNESS FOR A PARTICULAR PURPOSE.

import React from 'react'
import WizardSDK from './pages/WizardSDK'
import { BrowserRouter } from 'react-router-dom'
import WizardRoutes from './pages/WizardRoutes'
import { WizardProvider } from './context/WizardContext'
import './globals.css'

const App: React.FC = () => {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 text-slate-900">
<WizardSDK />
</div>
<BrowserRouter>
<WizardProvider>
<WizardRoutes />
</WizardProvider>
</BrowserRouter>
)
}

Expand Down
175 changes: 175 additions & 0 deletions templates/default/client/src/context/WizardContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

import React, { createContext, useContext, useEffect, useMemo, useCallback, useState, ReactNode } from 'react'
import { useAccount } from 'wagmi'
import { useEnclaveSDK, UseEnclaveSDKReturn } from '@enclave-e3/react'
import { getEnclaveSDKConfig } from '@/utils/sdk-config'

// ============================================================================
// TYPES & ENUMS
// ============================================================================

export enum WizardStep {
CONNECT_WALLET = 1,
REQUEST_COMPUTATION = 2,
ACTIVATE_E3 = 3,
ENTER_INPUTS = 4,
ENCRYPT_SUBMIT = 5,
RESULTS = 6,
}

export interface E3State {
id: bigint | null
isRequested: boolean
isCommitteePublished: boolean
isActivated: boolean
publicKey: `0x${string}` | null
expiresAt: bigint | null
plaintextOutput: string | null
hasPlaintextOutput: boolean
}

interface WizardContextType {
currentStep: WizardStep
submittedInputs: { input1: string; input2: string } | null
lastTransactionHash: string | undefined
inputPublishError: string | null
inputPublishSuccess: boolean
result: number | null
e3State: E3State

// Setters
setCurrentStep: (step: WizardStep) => void
setSubmittedInputs: (inputs: { input1: string; input2: string } | null) => void
setLastTransactionHash: (hash: string | undefined) => void
setInputPublishError: (error: string | null) => void
setInputPublishSuccess: (success: boolean) => void
setResult: (result: number | null) => void
setE3State: (state: E3State | ((prev: E3State) => E3State)) => void

// Handlers
handleReset: () => void
handleTryAgain: () => void

// SDK
sdk: UseEnclaveSDKReturn
}

const WizardContext = createContext<WizardContextType | undefined>(undefined)

export const useWizard = () => {
const context = useContext(WizardContext)
if (!context) {
throw new Error('useWizard must be used within a WizardProvider')
}
return context
}

interface WizardProviderProps {
children: ReactNode
}

/**
* WizardProvider component - Provides the WizardContext to the application
*
* This component is used to provide the WizardContext to the application,
* which is used to manage the wizard state and logic.
*/
export const WizardProvider: React.FC<WizardProviderProps> = ({ children }) => {
const { isConnected } = useAccount()

// Memoize the SDK config to prevent unnecessary re-initializations.
const sdkConfig = useMemo(() => getEnclaveSDKConfig(), [])
const sdk = useEnclaveSDK(sdkConfig)

const [currentStep, setCurrentStep] = useState<WizardStep>(WizardStep.CONNECT_WALLET)
const [submittedInputs, setSubmittedInputs] = useState<{ input1: string; input2: string } | null>(null)
const [lastTransactionHash, setLastTransactionHash] = useState<string | undefined>(undefined)
const [inputPublishError, setInputPublishError] = useState<string | null>(null)
const [inputPublishSuccess, setInputPublishSuccess] = useState<boolean>(false)
const [result, setResult] = useState<number | null>(null)
const [e3State, setE3State] = useState<E3State>({
id: null,
isRequested: false,
isCommitteePublished: false,
isActivated: false,
publicKey: null,
expiresAt: null,
plaintextOutput: null,
hasPlaintextOutput: false,
})

// Auto-advance steps based on state.
useEffect(() => {
if (!isConnected) {
setCurrentStep(WizardStep.CONNECT_WALLET)
} else if (sdk.isInitialized && currentStep === WizardStep.CONNECT_WALLET) {
setCurrentStep(WizardStep.REQUEST_COMPUTATION)
}
}, [isConnected, sdk.isInitialized, currentStep])

const handleReset = useCallback(() => {
setCurrentStep(WizardStep.CONNECT_WALLET)
setSubmittedInputs(null)
setLastTransactionHash(undefined)
setInputPublishError(null)
setInputPublishSuccess(false)
setResult(null)
setE3State({
id: null,
isRequested: false,
isCommitteePublished: false,
isActivated: false,
publicKey: null,
expiresAt: null,
plaintextOutput: null,
hasPlaintextOutput: false,
})
}, [])

const handleTryAgain = useCallback(() => {
setCurrentStep(WizardStep.ENTER_INPUTS)
setInputPublishError(null)
setInputPublishSuccess(false)
}, [])

const contextValue: WizardContextType = useMemo(
() => ({
currentStep,
submittedInputs,
lastTransactionHash,
inputPublishError,
inputPublishSuccess,
result,
e3State,
setCurrentStep,
setSubmittedInputs,
setLastTransactionHash,
setInputPublishError,
setInputPublishSuccess,
setResult,
setE3State,
handleReset,
handleTryAgain,
sdk,
}),
[
currentStep,
submittedInputs,
lastTransactionHash,
inputPublishError,
inputPublishSuccess,
result,
e3State,
handleReset,
handleTryAgain,
sdk,
],
)

return <WizardContext.Provider value={contextValue}>{children}</WizardContext.Provider>
}
96 changes: 96 additions & 0 deletions templates/default/client/src/pages/WizardRoutes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

import React, { useMemo } from 'react'
import { Routes, Route, Navigate } from 'react-router-dom'
import {
NumberSquareOneIcon,
NumberSquareTwoIcon,
NumberSquareThreeIcon,
NumberSquareFourIcon,
NumberSquareFiveIcon,
NumberSquareSixIcon,
} from '@phosphor-icons/react'

// Step components
import ConnectWallet from './steps/ConnectWallet'
import RequestComputation from './steps/RequestComputation'
import ActivateE3 from './steps/ActivateE3'
import EnterInputs from './steps/EnterInputs'
import EncryptSubmit from './steps/EncryptSubmit'
import Results from './steps/Results'
import EnvironmentError from './components/EnvironmentError'
import SDKErrorDisplay from './components/SDKErrorDisplay'
import StepIndicator from './components/StepIndicator'
import { useWizard, WizardStep } from '../context/WizardContext'
import { MISSING_ENV_VARS } from '@/utils/env-config'

interface StepConfig {
step: WizardStep
path: string
component: React.ComponentType
icon: React.ComponentType<any>
}

// Steps are defined below as an array of objects.
// Each entry specifies the wizard step, URL path, component, and icon for that step.
const STEPS: StepConfig[] = [
{ step: WizardStep.CONNECT_WALLET, path: '/step1', component: ConnectWallet, icon: NumberSquareOneIcon },
{ step: WizardStep.REQUEST_COMPUTATION, path: '/step2', component: RequestComputation, icon: NumberSquareTwoIcon },
{ step: WizardStep.ACTIVATE_E3, path: '/step3', component: ActivateE3, icon: NumberSquareThreeIcon },
{ step: WizardStep.ENTER_INPUTS, path: '/step4', component: EnterInputs, icon: NumberSquareFourIcon },
{ step: WizardStep.ENCRYPT_SUBMIT, path: '/step5', component: EncryptSubmit, icon: NumberSquareFiveIcon },
{ step: WizardStep.RESULTS, path: '/step6', component: Results, icon: NumberSquareSixIcon },
]

/**
* WizardRoutes component that manages the multi-step wizard flow for Enclave E3.
* Handles routing between wizard steps, displays step indicators, and manages
* error states for environment configuration and SDK errors.
*
* Dynamically sets up routes for each wizard step, only rendering the component
* for the current step and redirecting to the currentStep's route otherwise.
* This enforces linear navigation through the wizard.
*/
const WizardRoutes: React.FC = () => {
const { currentStep, sdk } = useWizard()

// Memoize the current step path to avoid unnecessary recalculations.
const currentStepPath = useMemo(() => `/step${currentStep}`, [currentStep])

// Early returns for error states.
if (MISSING_ENV_VARS.length > 0) {
return <EnvironmentError missingVars={MISSING_ENV_VARS} />
}

if (sdk.error) {
return <SDKErrorDisplay error={sdk.error} />
}

return (
<div className='min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 text-slate-900'>
<div className='container mx-auto px-4 py-8'>
<div className='mb-8 text-center'>
<h1 className='mb-2 text-4xl font-bold text-slate-800'>Enclave E3</h1>
<p className='text-lg text-slate-600'>Confidential computation with Enclave Encrypted Execution Environments.</p>
</div>

<StepIndicator currentStep={currentStep} steps={STEPS} />

<div className='mx-auto max-w-2xl'>
<Routes>
<Route path='/' element={<Navigate to={currentStepPath} replace />} />
{STEPS.map(({ step, path, component: Component }) => (
<Route key={path} path={path} element={currentStep === step ? <Component /> : <Navigate to={currentStepPath} replace />} />
))}
</Routes>
</div>
</div>
</div>
)
}

export default WizardRoutes
Loading
Loading