Monorepo hosting the products built on top of ACTA — trust-minimized Verifiable Credentials on Stellar/Soroban.
apps/web— products catalog / landing.apps/credit-history— portable credit history / financial inclusion product (skeleton).packages/ui— shared React components (Tailwind v4).packages/acta— thin integration layer over@acta-team/credentials.packages/config— shared tsconfig / eslint presets.packages/types— shared TypeScript types.
Stack: Next.js 16, React 19, Tailwind CSS v4, TypeScript 5 · pnpm workspaces + Turborepo · Node >= 22.
corepack enable # once, to get the pinned pnpm
pnpm install
pnpm dev # web on :3000, credit-history on :3001pnpm dev— start all apps in dev modepnpm build— production build of every app/packagepnpm lint— lint everythingpnpm typecheck—tsc --noEmiteverywherepnpm format— Prettier over the repo
The app reads credentials through a single useCredentialSource() hook
(apps/credit-history/src/lib/use-credential-source.ts), so /credentials,
/credentials/[id], /share and /vault always agree on what the connected
holder owns. /verify/[token] (the public verifier) is unauthenticated and
resolves its own owner from the shared presentation's holder DID instead.
Which mode is active is controlled by NEXT_PUBLIC_DATA_SOURCE:
mock(default, no env var needed) — every surface reads the fixtures inpackages/acta/src/mock.ts. No wallet and no testnet credentials are required to try/credentials,/share, or/verify/[token]end to end./vaultstill expects a wallet click, but withNEXT_PUBLIC_WALLETunset (see below) it accepts a mock connect with no real extension installed.real— session-gated surfaces (/credentials,/credentials/[id],/share,/vault) read the connected holder's actual vault throughActaCredentialSource, and show the wallet-connect prompt instead of fixtures until a wallet is connected. This requires a Stellar testnet (or mainnet) account holding real ACTA credentials, plusNEXT_PUBLIC_ACTA_API_KEYfor the ACTA SDK client (apps/credit-history/src/providers/acta-provider.tsx). A real-mode call that somehow reachesgetCredentialSource()with no owner (e.g. a bug, or a holder DID the public verifier can't parse) logs aconsole.warnand falls back to mock — it never fails silently.
Related env vars:
NEXT_PUBLIC_WALLET=real— use the Stellar Wallets Kit / Freighter connector instead of the mock wallet connector (apps/credit-history/src/session/wallet-connector.ts). Unset or any other value uses the mock connector, which returns a fixed address with no extension required — useful for contributors without a wallet installed, in either data-source mode.NEXT_PUBLIC_STELLAR_NETWORK—testnet(default) ormainnet.NEXT_PUBLIC_MOCK_MODE—emptyorerror, to exercise the empty/error states of the mock source; anything else behaves asnormal.
Share links (/api/presentations) are persisted through PresentationStore
(apps/credit-history/src/lib/presentation-store.ts). The driver is chosen by
PRESENTATION_STORE_DRIVER:
-
memory(default, no env var needed) — an in-processMap. Zero-config forpnpm dev, but it does not survive a restart and is not shared between serverless instances — fine for local development, not for a real deploy. -
upstash-redis— durable, HTTP-based Redis (no persistent connection to manage across serverless invocations) with native per-key TTL that mirrors each link's expiration, so expired links free themselves without a cron job. Requires:UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN
Provision a free database at upstash.com, then copy its REST URL/token from the database dashboard.
A holder lists or revokes their own links (/vault) by signing an action
message with their wallet — see holderActionMessage() in
presentation-store.ts. Knowing a holder's DID is never enough on its own to
list or revoke their links.