Skip to main content

Onboarding Journey

How a new participant arrives, gets an on-chain identity, and joins their first garden. Two parallel entry paths converge on the same Gardener smart account: the client PWA passkey path for users with smartphones, and the agent (Telegram/SMS) path for low-bandwidth field workers.

Personas

  • A: Gardener — primary subject. Either path delivers a smart account, a gardener role, and a current-garden assignment.
  • B: Operator — separate path. Operators authenticate via wallet (AppKit) directly into the admin dashboard and gain operator role from on-chain hat membership; they do not pass through the gardener onboarding flow.

State machine

Entry points

EntrySurfaceTrigger
Client PWApackages/client/src/views/Login/index.tsxUser opens the PWA URL. useInstallGuidance decides whether to prompt install.
Telegram botpackages/agent/src/handlers/start.tsUser sends /start in Telegram (or any first message).
Admin dashboardpackages/admin/src/views/Profile/index.tsxOperator opens admin URL, connects wallet via AppKit.

Steps

#StatePersonaSurface (package + view)Hook / ServiceSide effectsStatus
1ChoosingAuthAclient / views/LoginuseAuth, useInstallGuidance, useAppNone — UI onlyshipped
2aCreatingPasskeyAclient / views/LoginuseAuth.createPasskey()navigator.credentials.create(); with VITE_PASSKEY_SERVER_ENABLED=true, Pimlico hosted passkey server verifies the credential against normalized username/ENS context; localStorage caches metadata for same-device fallbackshipped
2bWalletConnectA or Bclient / views/Login, admin / views/ProfileuseAppKit, useAuth.connect()Wallet connection event from AppKitshipped
3SmartAccountReadyAclient (cross-cutting)useAuth, internal smart account derivationCREATE2 address derived; ERC-4337 account deployed lazily on first txshipped
4AutoJoinedRootAclient (cross-cutting)useAutoJoinRootGardenReads VITE_ROOT_GARDEN, queues a join via JoinGarden flow when address present and not yet joinedshipped
5AgentStartAagent / handlers/start.tsdb.createUser (custodial private key generated by generatePrivateKey)DB row created; address returned to user via Telegram messageshipped
6AwaitingJoin → JoinedGardenAagent / handlers/join.tsblockchain.getGardenInfo, db.updateUsercurrentGarden written to user row; no on-chain tx at this stageshipped
7GardenerAclient / views/Home (after PWA path), Telegram chat (after agent path)useGardens, useGardenerFirst on-chain interaction (work submission) deploys the smart account via paymastershipped

Failure / recovery paths

  • Passkey recovery after local cache loss. Login/index.tsx asks for the username or ENS handle before lookup. With VITE_PASSKEY_SERVER_ENABLED=true, the shared auth service calls Pimlico's hosted passkey server using the same normalized context used at registration. Synced passkeys can recover where the user's passkey provider supports sync. Legacy local-only passkeys still work on the same device and may require re-enrollment after storage loss.
  • Passkey unavailable or server unavailable. Login/index.tsx surfaces retry/fallback guidance. The shared auth service preserves credential, username, auth mode, and expected-address metadata on server/network failure, then falls back to the same-device local credential when one exists.
  • Address mismatch. If server-discovered credential metadata rebuilds a different stored smart-account address, the shared auth service fails closed and the UI treats it as a recoverable failure. It does not silently create a new account.
  • In-app browser scenario. useInstallGuidance returns scenario: "in-app-browser" (Telegram, Twitter, etc. embedded webview). UI surfaces "Open in Chrome/Safari" with a copy-link affordance and blocks passkey ceremonies until the user opens a supported browser.
  • Garden address invalid (/join agent path). blockchain.getGardenInfo returns {exists: false}. Bot replies with "Garden not found" and does not mutate user record.
  • Custodial key compromise (agent path). No automated rotation today. The bot holds the private key; recovery is operationally manual. Spec § 3.1 calls out this limitation as accepted for the field-worker JTBD ("low bandwidth submission") — the trade-off is that gardeners do not manage keys themselves.
  • Smart account first-tx failure. Paymaster funds exhausted, RPC unavailable, or bundler rejects the UserOp. Tx is retried via the standard mutation pipeline (useTransactionSender). PWA does not deploy the smart account until needed (counterfactual deployment).

Connections

  • Work submission (./work-submission) — the next journey after onboarding for Persona A. The first work submission also deploys the smart account on-chain via the paymaster.
  • Funding (./funding) — Persona D's onboarding to garden depositing reuses the same wallet-connect flow at Public/Fund.tsx.
  • See also: the Passkey onboarding sequence diagram.

Notes for builders

  • Hook boundary: all auth hooks live in @green-goods/shared (hooks/auth/). Client and admin only consume them.
  • Single chain: the chain is fixed at build time by VITE_CHAIN_ID. There is no chain-switching UX during onboarding.
  • LocalStorage is a cache and same-device fallback for passkey credential metadata, normalized username, RP ID, and expected smart-account address. It is not the recovery source of truth when the hosted server path is enabled.
  • VITE_PASSKEY_SERVER_ENABLED=false is the rollback path and keeps legacy local-only passkey behavior.
  • Green Goods does not currently import legacy local-only credentials into Pimlico's hosted passkey server, and this lane does not promise same-address recovery after total passkey-provider loss.