GregoSwap is a decentralized token swap application built on the Aztec blockchain. It demonstrates private token swaps using an Automated Market Maker (AMM), with a token faucet (drip) mechanism using proof-of-password.
Key Features:
- Private token swaps (GregoCoin ↔ GregoCoinPremium) via AMM
- Token faucet with proof-of-password (PoP) contract
- Multi-flow onboarding supporting embedded and external wallets
- Network switching (local sandbox / devnet)
Tech Stack:
- React 18 + TypeScript
- Material-UI (MUI) component library
- Vite build tooling
- Aztec SDK (@aztec/aztec.js, @aztec/wallet-sdk)
- Yarn 4.5.2 package manager
src/
├── contexts/ # State management (Context + Reducer pattern)
├── services/ # Pure functions for wallet/contract operations
├── components/ # React UI components
├── config/ # Network configuration
├── types/ # Shared type definitions
└── utils/ # Utility functions
<NetworkProvider>
<WalletProvider>
<ContractsProvider>
<OnboardingProvider>
<SwapProvider>
<App />
</SwapProvider>
</OnboardingProvider>
</ContractsProvider>
</WalletProvider>
</NetworkProvider>Each context uses a colocated reducer pattern with a factory function:
src/contexts/
├── utils.ts # createReducerHook factory
├── network/
│ ├── NetworkContext.tsx # No reducer (simple state)
│ └── index.ts
├── wallet/
│ ├── WalletContext.tsx
│ ├── reducer.ts # State, actions, reducer, hook
│ └── index.ts
├── contracts/
│ ├── ContractsContext.tsx
│ ├── reducer.ts
│ └── index.ts
├── onboarding/
│ ├── OnboardingContext.tsx
│ ├── reducer.ts
│ └── index.ts
└── swap/
├── SwapContext.tsx
├── reducer.ts
└── index.ts
// Creates a hook that returns [state, boundActions] tuple
export const useSwapReducer = createReducerHook(swapReducer, swapActions, initialSwapState);
// Usage in context:
const [state, actions] = useSwapReducer();
actions.setFromAmount('100'); // Type-safe, no dispatch() neededKey benefits:
- Type-safe action creators
- No dispatch callback boilerplate
- Actions bound automatically via
bindActions()
Each reducer.ts exports:
- State type and initial state (e.g.,
SwapState,initialSwapState) - Actions object with action creators (e.g.,
swapActions) - Action union type via
ActionsFrom<typeof actions> - Reducer function (e.g.,
swapReducer) - Hook created via
createReducerHook()(e.g.,useSwapReducer)
Purpose: Network selection and configuration
State:
activeNetwork: NetworkConfig- Currently selected networkavailableNetworks: NetworkConfig[]- All discovered networksisLoading: boolean
Key behavior:
- Loads network configs from
src/config/networks/deployed-addresses.json - Persists selection to localStorage
- Excludes local network in production builds
Purpose: Wallet instance management (embedded vs external)
State:
wallet: Wallet | null- Active walletnode: AztecNode | null- Aztec node clientcurrentAddress: AztecAddress | null- Selected accountisUsingEmbeddedWallet: boolean- Wallet type flagisLoading: boolean/error: string | null
Key methods:
discoverWallets(timeout?): DiscoverySession
initiateConnection(provider): Promise<PendingConnection>
confirmConnection(provider, pending): Promise<Wallet>
cancelConnection(pending): void
setCurrentAddress(address): void
disconnectWallet(): Promise<void>
onWalletDisconnect(callback): () => void // Returns unsubscribeKey behavior:
- Auto-creates embedded wallet on network change
- Manages disconnect callback registry
- Reverts to embedded wallet on external disconnect
Purpose: Contract instances and registration
State:
contracts: { gregoCoin, gregoCoinPremium, amm, pop }isLoading: boolean
Key methods:
registerBaseContracts(): Promise<void> // AMM + tokens
registerDripContracts(): Promise<void> // PoP contract
getExchangeRate(): Promise<number>
fetchBalances(): Promise<[bigint, bigint]>
simulateOnboardingQueries(): Promise<[rate, gcBal, gcpBal]>
swap(amountOut, amountInMax): Promise<TxReceipt>
drip(password, recipient): Promise<TxReceipt>Purpose: Orchestrates multi-step onboarding flow
Status flow:
idle → connecting → registering → simulating →
[if balance=0] → registering_drip → awaiting_drip → executing_drip →
completed
State:
status: OnboardingStatus- Current flow stateresult: OnboardingResult | null- Simulation resultsneedsDrip: boolean- User needs to claim tokensdripPassword: string | null- PoP passworddripPhase: DripPhase- Drip execution phasependingSwap: boolean- Swap queued after onboardinghasRegisteredBase/hasSimulated- Tracking flags
Key behavior:
- Effects drive automatic state transitions
- Checks balance after simulation to determine drip need
- Persists completion to localStorage per address
Purpose: Swap UI state and execution
State:
fromAmount: string/toAmount: stringexchangeRate: number | nullisLoadingRate: booleanphase: SwapPhase- 'idle' | 'sending' | 'success' | 'error'error: string | null
Computed values (in context):
fromAmountUSD/toAmountUSDcanSwap- Whether swap button is enabledisSwapping- phase === 'sending'
Pure functions in src/services/ - contexts call these:
createNodeClient(nodeUrl): AztecNode
createEmbeddedWallet(node): Promise<EmbeddedWallet>
getChainInfo(network): ChainInfo
discoverWallets(chainInfo, timeout?): DiscoverySession
initiateConnection(provider): Promise<PendingConnection>
confirmConnection(pending): Promise<Wallet>
cancelConnection(pending): void
disconnectProvider(provider): Promise<void>// Registration
registerSwapContracts(wallet, node, network): Promise<SwapContracts>
registerDripContracts(wallet, node, network): Promise<DripContracts>
// Queries
getExchangeRate(wallet, contracts, fromAddress): Promise<number>
fetchBalances(wallet, contracts, address): Promise<[bigint, bigint]>
simulateOnboardingQueries(wallet, contracts, address): Promise<OnboardingResult>
// Execution
executeSwap(contracts, fromAddress, amountOut, amountInMax): Promise<TxReceipt>
executeDrip(pop, password, recipient): Promise<TxReceipt>
// Error parsing
parseSwapError(error): string
parseDripError(error): stringsrc/components/
├── App.tsx # Root component
├── OnboardingModal.tsx # Onboarding flow orchestrator
├── WalletChip.tsx # Header wallet button
├── NetworkSwitcher.tsx # Network dropdown
├── FooterInfo.tsx # Footer display
├── GregoSwapLogo.tsx # Logo
├── swap/
│ ├── SwapContainer.tsx # Main swap interface
│ ├── SwapBox.tsx # Token input with balance
│ ├── SwapButton.tsx # Execute button
│ ├── ExchangeRateDisplay.tsx
│ ├── SwapProgress.tsx # Transaction progress
│ └── SwapErrorAlert.tsx # Error display
└── onboarding/
├── OnboardingProgress.tsx # Progress indicator
├── WalletDiscovery.tsx # Scanning for wallets
├── WalletSelection.tsx # Wallet list
├── EmojiVerification.tsx # Secure channel verification
├── EmojiGrid.tsx # Emoji selection grid
├── AccountSelection.tsx # Account list
├── ConnectingWallet.tsx # Connection status
├── DripPasswordInput.tsx # PoP password form
├── FlowMessages.tsx # Status messages
└── CompletionTransition.tsx # Success animation
interface NetworkConfig {
id: string;
name: string;
nodeUrl: string;
chainId: string;
rollupVersion: string;
contracts: {
gregoCoin: string;
gregoCoinPremium: string;
amm: string;
liquidityToken: string;
pop: string;
salt: string;
};
deployer: { address: string };
deployedAt: string;
}
interface Balances {
gregoCoin: bigint | null;
gregoCoinPremium: bigint | null;
}
const GREGOCOIN_USD_PRICE = 10;
const EXCHANGE_RATE_POLL_INTERVAL_MS = 10000;App mounts
→ NetworkProvider loads deployed-addresses.json
→ WalletProvider creates embedded wallet + node
→ ContractsProvider registers base contracts (embedded wallet)
→ OnboardingProvider ready for external wallet flow
User clicks "Connect Wallet"
→ startOnboarding() → status='connecting'
→ OnboardingModal discovers wallets
→ User selects wallet → initiateConnection()
→ User verifies emojis → confirmConnection()
→ OnboardingContext detects wallet → registerBaseContracts()
→ simulateOnboardingQueries() → check balance
→ IF balance=0: registerDripContracts() → await password → drip()
→ ELSE: complete onboarding
User enters fromAmount
→ setFromAmount() calculates toAmount
→ User clicks "Swap"
→ executeSwap() sends transaction
→ phase: sending → mining → success
→ SwapContainer refetches balances
Located in contracts/:
-
GregoCoin & GregoCoinPremium (TokenContract)
- Standard Aztec token contracts
-
AMM (AMMContract)
swap_tokens_for_exact_tokens()method
-
ProofOfPassword (ProofOfPasswordContract)
check_password_and_mint()method- Uses SponsoredFPC for fee payment
- Node polyfills: buffer, path for browser
- WASM headers: Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy
- Chunk size limits: Main bundle < 1500KB, others < 8000KB
yarn serve # Dev server
yarn build # Production build
yarn compile:contracts # Compile Noir contracts
yarn deploy:local # Deploy to local sandbox
yarn deploy:devnet # Deploy to devnet
yarn test # Run contract tests# Terminal 1: Start Aztec sandbox
aztec start --local-network
# Terminal 2: Deploy contracts
PASSWORD=test123 yarn deploy:local
# Terminal 3: Run dev server
yarn serveColor palette:
- Primary: Chartreuse green (#D4FF28) - Aztec branded
- Secondary: Deep purple (#80336A)
- Background: Pure black (#000000)
- Text: Light parchment (#F2EEE1)
Typography: Geist font family
Effects check preconditions and advance state automatically:
// When wallet connects, mark registered and advance
useEffect(() => {
if (status === 'connecting' && wallet && currentAddress) {
actions.markRegistered();
actions.advanceStatus('registering');
}
}, [status, wallet, currentAddress]);- Embedded wallet: Auto-register on mount
- External wallet: Register during onboarding
- Two stages: Base (swap) then Drip (PoP) if needed
Used for exchange rate calculations with 18 decimal precision.
-
Don't use re-exports - Import directly from specific files
// WRONG import { useWallet } from '../contexts'; // RIGHT import { useWallet } from '../contexts/wallet';
-
Keep reducer actions namespaced - e.g.,
'swap/SET_RATE' -
Effect dependencies - OnboardingContext effects must include all dependencies to avoid stale closures
-
Balance checks - Embedded wallet can't query private balances; only external wallets show balances
-
Network switching - Requires wallet disconnect and state reset
src/main.tsx- Provider hierarchysrc/contexts/*/reducer.ts- State machinessrc/contexts/*/Context.tsx- Context providers with effectssrc/services/*.ts- Business logic (pure functions)src/components/OnboardingModal.tsx- Onboarding orchestrationsrc/components/swap/SwapContainer.tsx- Swap UI logic
vite.config.ts- Build configurationtsconfig.json- TypeScript configpackage.json- Dependencies and scriptssrc/config/networks/- Network config loader
- Aztec SDK: v4.0.0-nightly.20260205
- React: 18.3.1
- Vite: 7.1.4
- Node.js: v22+
- Yarn: 4.5.2