# Home | Cardless ID

> Markdown mirror of DialtoneApp's public top-site detail page for `cardlessid.org`.

URL: https://dialtoneapp.com/top-sites/cardlessid.org/index.md
Canonical HTML: https://dialtoneapp.com/top-sites/cardlessid.org

## Summary

- Domain: `cardlessid.org`
- Website: https://cardlessid.org
- Description: ai readable | score 16 | purchase read only
- Label: ai_readable
- Payment surface: Not available
- Purchase boundary: read_only
- Control boundary: unknown
- Rank: 202

## robots

~~~text
# As a condition of accessing this website, you agree to abide by the following
# content signals:

# (a)  If a Content-Signal = yes, you may collect content for the corresponding
#      use.
# (b)  If a Content-Signal = no, you may not collect content for the
#      corresponding use.
# (c)  If the website operator does not include a Content-Signal for a
#      corresponding use, the website operator neither grants nor restricts
#      permission via Content-Signal with respect to the corresponding use.

# The content signals and their meanings are:

# search:   building a search index and providing search results (e.g., returning
#           hyperlinks and short excerpts from your website's contents). Search does not
#           include providing AI-generated search summaries.
# ai-input: inputting content into one or more AI models (e.g., retrieval
#           augmented generation, grounding, or other real-time taking of content for
#           generative AI search answers).
# ai-train: training or fine-tuning AI models.

# ANY RESTRICTIONS EXPRESSED VIA CONTENT SIGNALS ARE EXPRESS RESERVATIONS OF
# RIGHTS UNDER ARTICLE 4 OF THE EUROPEAN UNION DIRECTIVE 2019/790 ON COPYRIGHT
# AND RELATED RIGHTS IN THE DIGITAL SINGLE MARKET.

# BEGIN Cloudflare Managed content

User-agent: *
Content-Signal: search=yes,ai-train=no
Allow: /

User-agent: Amazonbot
Disallow: /

User-agent: Applebot-Extended
Disallow: /

User-agent: Bytespider
Disallow: /

User-agent: CCBot
Disallow: /

User-agent: ClaudeBot
Disallow: /

User-agent: CloudflareBrowserRenderingCrawler
Disallow: /

User-agent: Google-Extended
Disallow: /

User-agent: GPTBot
Disallow: /

User-agent: meta-externalagent
Disallow: /

# END Cloudflare Managed Content

# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
~~~

## llms

~~~text
# Cardless ID

> Decentralized identity credential system built on Algorand blockchain with W3C Verifiable Credentials

**Latest Version:** This documentation is also available at `https://cardlessid.org/llms.txt` for remote access with tools like `blz`.

## Project Overview

Cardless ID is a privacy-focused, decentralized identity verification platform that issues W3C-compliant verifiable credentials on the Algorand blockchain. The system enables one-time identity verification with cryptographic proofs, designed primarily for age verification across websites while maintaining user privacy.

**Key Features:**
- W3C Verifiable Credential standard compliance
- Algorand blockchain integration for credential storage
- Sybil-resistant duplicate detection via composite hashing
- Privacy-first architecture with no persistence of PII data
- Mobile wallet support for credential management
- QR code-based verification flow

**Primary Use Case:** Age verification for adult content sites (24 US states + UK, France, Germany require age verification by law)

## Architecture

### Application Structure

```
.
|-- app/
    |-- components/          # React components
    |   |-- credentials/     # W3C credential templates
    |   +-- verification/    # Identity verification UI
    |-- routes/             # React Router v7 routes
    |   |-- api/            # API endpoints
    |   +-- app/            # Application pages
    |-- utils/              # Utility functions
    |-- layouts/            # Layout components
    +-- hooks/              # Custom React hooks
|-- docs/                   # Documentation
+-- scripts/                # Build and test scripts
```

### Technology Stack

- **Framework:** React Router v7 (framework mode)
- **Language:** TypeScript
- **Styling:** Tailwind CSS + DaisyUI
- **Database:** Firebase Realtime Database
- **Blockchain:** Algorand (testnet/mainnet)
- **Identity Verification:** AWS Textract, Google Document AI, AWS Rekognition
- **Credential Standard:** W3C Verifiable Credentials v2

### Import Path Alias

Use `~` as an alias for `/app` in all imports:
```typescript
import { something } from '~/utils/helper';
```

## Core Concepts

### W3C Verifiable Credentials

Cardless ID issues W3C-compliant verifiable credentials with the following structure:

**Credential Schema:**
- `@context`: W3C contexts + custom Cardless context
- `id`: Unique credential identifier (UUID)
- `type`: ["VerifiableCredential", "BirthDateCredential"]
- `issuer`: DID (Decentralized Identifier) - `did:algo:WALLET_ADDRESS`
- `credentialSubject`: Contains composite hash of identity
- `evidence`: W3C standard verification metadata (fraud detection, OCR confidence, biometrics)
- `credentialStatus`: References Algorand issuer registry smart contract
- `service`: (Optional) System attestation linking to git commit of issuing code
- `proof`: Ed25519 signature for cryptographic verification

**Example Credential:** See [app/components/credentials/w3c-minimal.ts:30-92](app/components/credentials/w3c-minimal.ts)

### Composite Hash (Sybil Resistance)

The `compositeHash` field prevents duplicate credentials from being issued to the same person:

```typescript
// Format: SHA-256 hash of "firstName|middleName|lastName|birthDate"
compositeHash: "d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35"
```

This enables duplicate detection without storing personal data. See [app/utils/composite-hash.server.ts](app/utils/composite-hash.server.ts)

### Cryptographic Proof

Each credential includes an Ed25519 signature:
1. Credential is canonicalized (proof field removed)
2. Issuer signs with private key
3. Verifiers validate using issuer's public key (derived from Algorand address)

This ensures credentials cannot be forged. See [app/utils/data-integrity.server.ts](app/utils/data-integrity.server.ts)

### Evidence Property (Verification Quality)

The W3C-standard `evidence` property includes:
- **fraudDetection**: Google Document AI fraud signals
- **documentAnalysis**: AWS Textract OCR confidence, quality level (high/medium/low)
- **biometricVerification**: AWS Rekognition face match and liveness scores

This allows relying parties to make risk-based trust decisions. See [app/utils/credential-schema.ts:16-42](app/utils/credential-schema.ts)

### Service Property (System Attestation)

The optional `service` array provides transparency and auditability:

```json
"service": [
  {
    "id": "#system-attestation",
    "type": "SystemAttestation",
    "serviceEndpoint": "https://github.com/owner/repo/commit/abc123def456"
  }
]
```

**Purpose:**
- Links to the exact git commit of the code that issued the credential
- Enables anyone to audit the issuing code for security review
- Helps track which code version generated each credential
- Demonstrates commitment to transparent, auditable processes

**Implementation:**
- Git information is captured at build time via environment variables
- Package.json build script extracts commit hash, repo owner, and repo slug
- Only included when git information is available (not in development builds)
- See [app/utils/git-info.ts](app/utils/git-info.ts) for implementation

### Privacy Model (Transient Data)

**Zero Permanent Storage:**
- Government ID and selfie photos are processed and immediately deleted
- Identity data (name, birth date) is never stored in database
- Only HMAC hashes are kept for data integrity verification
- Full identity data is only available to credential holder in mobile wallet

**What's Stored:**
- Firebase: Session metadata (status, timestamps), HMAC hashes, composite hashes
- Algorand: 0-value transactions with credential metadata in note field
- No PII (Personally Identifiable Information) is permanently stored

### Algorand Integration

**Network Configuration:**
- Testnet: `https://testnet-api.algonode.cloud`
- Mainnet: `https://mainnet-api.algonode.cloud`

**Key Functions:**
- Wallet creation and funding
- Credential transaction creation (0 ALGO payment with note)
- Duplicate credential detection
- Balance checking and validation

See [app/utils/algorand.ts](app/utils/algorand.ts)

## API Endpoints

### Verification Flow

**Start Verification**
```
POST /api/verification/start
Body: { walletAddress: string }
Returns: { sessionId: string, expiresAt: string }
```

**Upload ID Document**
```
POST /api/verification/upload-id
Body: { sessionId: string, frontImage: base64, backImage?: base64 }
Returns: { success: boolean, extractedData?: object }
```

**Upload Selfie**
```
POST /api/verification/upload-selfie
Body: { sessionId: string, selfieImage: base64 }
Returns: { success: boolean, faceMatch?: { confidence: number } }
```

**Check Verification Status**
```
GET /api/verification/status/{sessionId}
Returns: { status: 'pending' | 'approved' | 'rejected', data?: object }
```

### Credential Issuance

**Create Credential**
```
POST /api/credentials
Body: {
  walletAddress: string,
  firstName: string,
  lastName: string,
  birthDate: string,
  sessionId: string
}
Returns: { credential: object, txId: string }
```

**Get Credential Schema**
```
GET /api/credentials/schema
Returns: W3C credential schema definition
```

**Transfer Credential**
```
POST /api/credentials/transfer
Body: { credential: object, recipientAddress: string }
Returns: { txId: string }
```

### Age Verification (QR Code Flow)

**Create Age Verification Challenge**
```
POST /api/age-verify/create
Body: { minimumAge: number, verifierName?: string }
Returns: { sessionId: string, qrCode: string, expiresAt: string }
```

**Respond to Challenge**
```
POST /api/age-verify/respond
Body: { sessionId: string, walletAddress: string, meetsAge: boolean }
Returns: { success: boolean }
```

**Check Challenge Status**
```
GET /api/age-verify/session/{sessionId}
Returns: { status: 'pending' | 'approved' | 'rejected', meetsAge?: boolean }
```

### Wallet Status

**Check Wallet Status**
```
GET /api/wallet/status/{address}
Returns: { hasCredential: boolean, isVerified: boolean }
```

### Stateless Verification API (Current — no API key required)

The preferred integration path. Uses HMAC-signed nonces and Algorand ed25519 proof signing. No API key or server-side session required.

**Get Nonce**
```
GET /api/v/nonce?minAge=21&siteId=optional
Returns: { nonce: string, deepLinkUrl: string, minAge: number, expiresAt: number }
```
The `nonce` is an HMAC-signed token encoding `minAge`, `siteId`, and a TTL. The `deepLinkUrl` is a QR-displayable deep link for the Cardless ID wallet app.

**Submit Proof (wallet → server)**
```
POST /api/v/submit
Body: { nonce: string, proof: SignedProof }
SignedProof: { payload: { nonce, walletAddress, minAge, meetsRequirement, timestamp }, signature: string }
Returns: { success: true } | { error: string }
```
The wallet signs `JSON.stringify(payload)` with the Algorand `algosdk.signBytes()` prefix ("MX"). The server caches the proof keyed by nonce for TTL polling.

**Poll for Result**
```
GET /api/v/result/:nonce
Returns: { status: 'pending' | 'approved' | 'rejected' | 'expired', proof?: SignedProof }
```

See [sdk/INTEGRATION_GUIDE.md](sdk/INTEGRATION_GUIDE.md) for the complete integration guide.

### Integrator API (Legacy — requires API key)

**Create Challenge**
```
POST /api/integrator/challenge/create
Headers: { Authorization: 'Bearer API_KEY' }
Body: { minimumAge: number, metadata?: object }
Returns: { challengeId: string, qrCodeUrl: string }
```

**Verify Challenge**
```
GET /api/integrator/challenge/verify/{challengeId}
Headers: { Authorization: 'Bearer API_KEY' }
Returns: { verified: boolean, walletAddress?: string, timestamp?: string }
```

**Challenge Details**
```
GET /api/integrator/challenge/details/{challengeId}
Headers: { Authorization: 'Bearer API_KEY' }
Returns: { challenge: object, responses: array }
```

See [docs/INTEGRATOR_README.md](docs/INTEGRATOR_README.md) for full integration guide.

## SDKs

### `@cardlessid/verify` — Browser SDK (Current)

Drop-in browser SDK for stateless age verification. No API key required.

- **Package:** `@cardlessid/verify` (npm)
- **CDN:** `https://cdn.cardlessid.org/verify/latest/cardlessid-verify.js` (IIFE, ~61KB minified)
- **Source:** `sdk/browser/src/index.ts`
- **Build:** `sdk/browser/build.mjs` (esbuild: IIFE + ESM + CJS + `.d.ts`)

**API:**
```typescript
// Constructor options
interface CardlessIDVerifyOptions {
  minAge?: number;          // default: 18
  siteId?: string;          // optional label for analytics
  pollInterval?: number;    // ms between polls (default: 2000)
  onVerified?: (proof: SignedProof) => void;   // called on meetsRequirement=true
  onResult?: (proof: SignedProof) => void;     // called on any completed proof
  onError?: (err: Error) => void;
}

const verifier = new CardlessIDVerify(options);
verifier.mount(selector: string | HTMLElement): void;  // injects QR UI
verifier.destroy(): void;                              // remove UI and stop polling
verifier.getNonce(): Promise<string>;                  // fetch a nonce directly
verifier.pollForResult(nonce, timeoutMs?): Promise<SignedProof>;
```

**Server-side proof verification:**
```typescript
import { verifyProof } from '@cardlessid/verify';
const result = verifyProof(proof);  // synchronous, no network
if (result.valid && result.payload.meetsRequirement) { /* allow */ }
```

`verifyProof` checks: ed25519 signature (Algorand MX prefix), timestamp within 5 minutes, `walletAddress` is valid Algorand address.

### `@cardlessid/verifier` — Node.js SDK (Legacy, requires API key)

Server-side SDK for the challenge-based integrator flow.

- **Package:** `@cardlessid/verifier` (npm)
- **Source:** `sdk/node/cardlessid-verifier.js`
- **Methods:** `createChallenge({ minAge, callbackUrl })`, `verifyChallenge(challengeId)`, `pollChallenge(challengeId, options)`

## Components

### Verification Components

**IdentityForm** - [app/components/verification/IdentityForm.tsx](app/components/verification/IdentityForm.tsx)
- Collects user identity information
- Props: `onSubmit(data: IdentityData): void`, `loading?: boolean`

**IdPhotoCapture** - [app/components/verification/IdPhotoCapture.tsx](app/components/verification/IdPhotoCapture.tsx)
- Camera interface for capturing government ID
- Props: `onCapture(image: string): void`, `requireBothSides?: boolean`

**SelfieCapture** - [app/components/verification/SelfieCapture.tsx](app/components/verification/SelfieCapture.tsx)
- Camera interface for selfie capture with liveness detection
- Props: `onCapture(image: string): void`, `showGuidance?: boolean`

**VerificationResult** - [app/components/verification/VerificationResult.tsx](app/components/verification/VerificationResult.tsx)
- Displays verification status and results
- Props: `status: 'pending' | 'approved' | 'rejected'`, `credential?: object`

### UI Components

**Header** - [app/components/Header.tsx](app/components/Header.tsx)
- Main navigation header with logo and menu

**Footer** - [app/components/Footer.tsx](app/components/Footer.tsx)
- Site footer with links and copyright

**CredentialQR** - [app/components/CredentialQR.tsx](app/components/CredentialQR.tsx)
- Generates QR code for credential sharing
- Props: `credential: object`, `size?: number`

**CodeBlock** - [app/components/CodeBlock.tsx](app/components/CodeBlock.tsx)
- Syntax-highlighted code display for documentation
- Props: `code: string`, `language: string`

**MermaidDiagram** - [app/components/MermaidDiagram.tsx](app/components/MermaidDiagram.tsx)
- Renders Mermaid diagram strings as SVG (client-side only, dynamic import)
- Props: `chart: string`, `className?: string`

## Utility Functions

### Verification Utils

**verification.server.ts** - [app/utils/verification.server.ts](app/utils/verification.server.ts)
- Core verification flow orchestration
- Functions: `startVerification()`, `processIdDocument()`, `processSelfie()`, `getVerificationStatus()`

**document-ai.server.ts** - [app/utils/document-ai.server.ts](app/utils/document-ai.server.ts)
- Google Document AI integration for fraud detection
- Functions: `detectFraud(imageBase64: string)`, `analyzeDocument()`

**textract.server.ts** - [app/utils/textract.server.ts](app/utils/textract.server.ts)
- AWS Textract integration for OCR
- Functions: `extractTextFromId()`, `analyzeIdDocument()`

**face-comparison.server.ts** - [app/utils/face-comparison.server.ts](app/utils/face-comparison.server.ts)
- AWS Rekognition for biometric matching
- Functions: `compareFaces()`, `detectLiveness()`

### Credential Utils

**credential-issuance.server.ts** - [app/utils/credential-issuance.server.ts](app/utils/credential-issuance.server.ts)
- Credential creation and issuance logic
- Functions: `issueCredential()`, `validateCredential()`, `revokeCredential()`

**credential-schema.ts** - [app/utils/credential-schema.ts](app/utils/credential-schema.ts)
- W3C credential schema definitions
- Exports: `CARDLESS_NAMESPACE`, `CARDLESS_FIELDS`, `SCHEMA_VERSION`, `USAGE_NOTES`

**data-integrity.server.ts** - [app/utils/data-integrity.server.ts](app/utils/data-integrity.server.ts)
- Cryptographic signing and verification
- Functions: `signCredential()`, `verifyProof()`, `createProof()`

**composite-hash.server.ts** - [app/utils/composite-hash.server.ts](app/utils/composite-hash.server.ts)
- Composite hash generation for sybil resistance
- Functions: `generateCompositeHash()`, `verifyCompositeHash()`

**nft-credentials.ts** - [app/utils/nft-credentials.ts](app/utils/nft-credentials.ts)
- NFT-based credential management on Algorand
- Functions: `createCredentialNFT()`, `transferCredentialNFT()`

### Algorand Utils

**algorand.ts** - [app/utils/algorand.ts](app/utils/algorand.ts)
- Algorand blockchain integration
- Key functions:
  - `isValidAlgorandAddress(address: string): Promise<boolean>`
  - `getAccountBalance(address: string): Promise<number>`
  - `fundNewWallet(issuer, recipient, amount): Promise<string>`
  - `createCredentialTransaction(...): Promise<string>`
  - `checkCredentialExists(issuer, user, hash): Promise<{exists, duplicateCount}>`

### Authentication & API

**auth.server.ts** - [app/utils/auth.server.ts](app/utils/auth.server.ts)
- Session management and authentication
- Functions: `createSession()`, `getSession()`, `destroySession()`

**api-auth.server.ts** - [app/utils/api-auth.server.ts](app/utils/api-auth.server.ts)
- API key validation for integrator endpoints
- Functions: `validateApiKey()`, `createApiKey()`, `revokeApiKey()`

**api-keys.server.ts** - [app/utils/api-keys.server.ts](app/utils/api-keys.server.ts)
- API key CRUD operations
- Functions: `generateApiKey()`, `storeApiKey()`, `lookupApiKey()`

### Firebase Utils

**firebase.server.ts** - [app/utils/firebase.server.ts](app/utils/firebase.server.ts)
- Firebase Admin SDK initialization and helpers
- Functions: `getDatabase()`, `getRef()`, `saveToDatabase()`, `queryDatabase()`

**firebase.config.ts** - [app/firebase.config.ts](app/firebase.config.ts)
- Firebase client SDK configuration (environment variables)

### Issuer Registry

**issuer-registry.ts** - [app/utils/issuer-registry.ts](app/utils/issuer-registry.ts)
- On-chain issuer registry management
- Functions: `registerIssuer()`, `checkIssuerStatus()`, `vouchForIssuer()`, `revokeIssuer()`

### Other Utils

**age-verification.server.ts** - [app/utils/age-verification.server.ts](app/utils/age-verification.server.ts)
- Age verification session management
- Functions: `createAgeVerificationSession()`, `getAgeVerificationSession()`, `updateAgeVerificationSession()`, `approveAgeVerificationSession()`, `rejectAgeVerificationSession()`, `purgeExpiredAgeVerificationSessions()`

**integrator-challenges.server.ts** - [app/utils/integrator-challenges.server.ts](app/utils/integrator-challenges.server.ts)
- Integrator API challenge handling
- Functions: `createIntegratorChallenge()`, `verifyIntegratorChallenge()`

**temp-photo-storage.server.ts** - [app/utils/temp-photo-storage.server.ts](app/utils/temp-photo-storage.server.ts)
- Temporary photo storage for verification (automatically deleted)
- Functions: `storePhoto()`, `retrievePhoto()`, `deletePhoto()`

**CardlessIssuerClient.ts** - [app/utils/CardlessIssuerClient.ts](app/utils/CardlessIssuerClient.ts)
- Client library for integrators to interact with Cardless ID
- Class: `CardlessIssuerClient` with methods for challenge creation and verification

## Routes

### Public Pages

- `/` - [app/routes/home.tsx](app/routes/home.tsx) - Homepage
- `/about` - [app/routes/about.tsx](app/routes/about.tsx) - About page
- `/what` - [app/routes/what.tsx](app/routes/what.tsx) - What is Cardless ID
- `/contact` - [app/routes/contact.tsx](app/routes/contact.tsx) - Contact form
- `/demo` - [app/routes/demo.tsx](app/routes/demo.tsx) - Interactive demo
- `/verify` - [app/routes/verify.tsx](app/routes/verify.tsx) - Public verification page
- `/w/:address` - [app/routes/w.$address.tsx](app/routes/w.$address.tsx) - Public wallet status

### Documentation Pages

- `/docs` - [app/routes/docs.tsx](app/routes/docs.tsx) - Documentation index
- `/docs/integration-guide` - [app/routes/docs/integration-guide.tsx](app/routes/docs/integration-guide.tsx) - Integration guide
- `/docs/credential-schema` - [app/routes/docs/credential-schema.tsx](app/routes/docs/credential-schema.tsx) - Credential schema docs
- `/docs/custom-verification-guide` - [app/routes/docs/custom-verification-guide.tsx](app/routes/docs/custom-verification-guide.tsx) - Custom verification
- `/docs/delegated-verification` - [app/routes/docs/delegated-verification.tsx](app/routes/docs/delegated-verification.tsx) - Delegated verification: API reference, request/response fields, error codes, and code examples for trusted issuers (banks, DMVs, etc.)
- `/docs/smart-contracts` - [app/routes/docs/smart-contracts.tsx](app/routes/docs/smart-contracts.tsx) - Smart contract architecture, Algorand ASA credential NFTs, issuance costs, and portability to other chains
- `/docs/verification-protocol` - [app/routes/docs/verification-protocol.tsx](app/routes/docs/verification-protocol.tsx) - How the stateless nonce-based verification protocol works for integrators, Algorand ed25519 proof signing, API reference (/api/v/*), and SIOP-OID4VP standards compatibility

### App Pages

- `/app/create-credential` - [app/routes/app/create-credential.tsx](app/routes/app/create-credential.tsx) - Credential creation flow
- `/app/verify` - [app/routes/app/verify.tsx](app/routes/app/verify.tsx) - Verify credential
- `/app/verify/:txId` - [app/routes/app/verify.$txId.tsx](app/routes/app/verify.$txId.tsx) - Verify specific transaction
- `/app/wallet-status` - [app/routes/app/wallet-status.tsx](app/routes/app/wallet-status.tsx) - Check wallet status
- `/app/wallet-verify` - [app/routes/app/wallet-verify.tsx](app/routes/app/wallet-verify.tsx) - Wallet verification
- `/app/wallet-verify-success` - [app/routes/app/wallet-verify-success.tsx](app/routes/app/wallet-verify-success.tsx) - Success page
- `/app/age-verify` - [app/routes/app/age-verify.tsx](app/routes/app/age-verify.tsx) - Age verification
- `/app/age-verify-success` - [app/routes/app/age-verify-success.tsx](app/routes/app/age-verify-success.tsx) - Age verify success
- `/app/age-verify-rejected` - [app/routes/app/age-verify-rejected.tsx](app/routes/app/age-verify-rejected.tsx) - Age verify rejected
- `/app/worldcoin` - [app/routes/app/worldcoin.tsx](app/routes/app/worldcoin.tsx) - Worldcoin integration
- `/app/mock-verification` - [app/routes/app/mock-verification.tsx](app/routes/app/mock-verification.tsx) - Mock verification for testing
- `/app/testnet-explorer` - [app/routes/app/testnet-explorer.tsx](app/routes/app/testnet-explorer.tsx) - Testnet explorer
- `/app/issuers` - [app/routes/app/issuers.tsx](app/routes/app/issuers.tsx) - Issuer registry management

### API Routes

All API routes return JSON responses. See [API Endpoints](#api-endpoints) section for details.

**Verification:**
- `POST /api/verification/start` - [app/routes/api/verification/start.ts](app/routes/api/verification/start.ts)
- `POST /api/verification/upload-id` - [app/routes/api/verification/upload-id.ts](app/routes/api/verification/upload-id.ts)
- `POST /api/verification/upload-selfie` - [app/routes/api/verification/upload-selfie.ts](app/routes/api/verification/upload-selfie.ts)
- `GET /api/verification/status/:id` - [app/routes/api/verification/status.$id.ts](app/routes/api/verification/status.$id.ts)
- `GET /api/verification/session/:sessionId` - [app/routes/api/verification/session.$sessionId.ts](app/routes/api/verification/session.$sessionId.ts)
- `POST /api/verification/webhook` - [app/routes/api/verification/webhook.ts](app/routes/api/verification/webhook.ts)

**Credentials:**
- `POST /api/credentials` - [app/routes/api/credentials.ts](app/routes/api/credentials.ts)
- `GET /api/credentials/schema` - [app/routes/api/credentials/schema.ts](app/routes/api/credentials/schema.ts)
- `POST /api/credentials/transfer` - [app/routes/api/credentials/transfer.ts](app/routes/api/credentials/transfer.ts)

**Age Verification:**
- `POST /api/age-verify/create` - [app/routes/api/age-verify/create.ts](app/routes/api/age-verify/create.ts)
- `POST /api/age-verify/respond` - [app/routes/api/age-verify/respond.ts](app/routes/api/age-verify/respond.ts)
- `GET /api/age-verify/session/:sessionId` - [app/routes/api/age-verify/session.$sessionId.ts](app/routes/api/age-verify/session.$sessionId.ts)

**Stateless Verification API (Current):**
- `GET /api/v/nonce` - fetch HMAC-signed nonce + deep link URL (query: `minAge`, `siteId`)
- `POST /api/v/submit` - wallet submits signed proof (body: `{ nonce, proof: SignedProof }`)
- `GET /api/v/result/:nonce` - poll for result; returns `{ status, proof? }`

**Integrator API (Legacy — requires API key):**
- `POST /api/integrator/challenge/create` - [app/routes/api/integrator/challenge/create.ts](app/routes/api/integrator/challenge/create.ts)
- `GET /api/integrator/challenge/verify/:challengeId` - [app/routes/api/integrator/challenge/verify.$challengeId.ts](app/routes/api/integrator/challenge/verify.$challengeId.ts)
- `GET /api/integrator/challenge/details/:challengeId` - [app/routes/api/integrator/challenge/details.$challengeId.ts](app/routes/api/integrator/challenge/details.$challengeId.ts)
- `POST /api/integrator/challenge/respond` - [app/routes/api/integrator/challenge/respond.ts](app/routes/api/integrator/challenge/respond.ts)

**Wallet:**
- `GET /api/wallet/status/:address` - [app/routes/api/wallet/status.$address.ts](app/routes/api/wallet/status.$address.ts)

**Other:**
- `GET /api/hello` - [app/routes/api/hello.ts](app/routes/api/hello.ts) - Health check
- `GET /api/announcements` - [app/routes/api/announcements.ts](app/routes/api/announcements.ts) - System announcements
- `POST /api/verify-worldcoin` - [app/routes/api/verify-worldcoin.ts](app/routes/api/verify-worldcoin.ts) - Worldcoin verification
- `POST /api/verify-webhook` - [app/routes/api/verify-webhook.ts](app/routes/api/verify-webhook.ts) - Verification webhook
- `POST /api/delegated-verification/issue` - [app/routes/api/delegated-verification/issue.ts](app/routes/api/delegated-verification/issue.ts) - Issue credential to a wallet on behalf of a trusted issuer; API key in request body; compositeHash uses firstName+lastName+dateOfBirth (no middleName)

**Credential V1 (legacy):**
- `GET /credentials/v1` - [app/routes/credentials/v1.ts](app/routes/credentials/v1.ts)

## Coding Conventions

### File Naming

- **Route files:** lowercase (e.g., `home.tsx`, `about.tsx`)
- **Route components:** PascalCase (e.g., `Home`, `About`)
- **Standalone components:** PascalCase (e.g., `Header.tsx`, `Footer.tsx`)
- **Utility files:** lowercase with hyphens (e.g., `algorand.ts`, `credential-schema.ts`)
- **Server-only files:** suffix with `.server.ts` (e.g., `verification.server.ts`)

### Import Conventions

```typescript
// Use ~ alias for app imports
import { Header } from '~/components/Header';
import { algorand } from '~/utils/algorand';

// External libraries first
import React from 'react';
import { useLoaderData } from 'react-router';

// Then internal imports
import { MyComponent } from '~/components/MyComponent';
import { myUtil } from '~/utils/myUtil';
```

### Component Structure

```typescript
// Props interface first
interface MyComponentProps {
  data: string;
  onAction?: () => void;
}

// Component definition
export function MyComponent({ data, onAction }: MyComponentProps) {
  // Hooks at top
  const [state, setState] = useState();

  // Event handlers
  const handleClick = () => {
    // ...
  };

  // Render
  return (
    <div>
      {/* JSX */}
    </div>
  );
}
```

### Server Functions

All server-side functions that interact with Firebase, AWS, or Algorand should be in `.server.ts` files:

```typescript
// app/utils/my-feature.server.ts
export async function serverFunction() {
  // Server-only code
}
```

### Error Handling

```typescript
try {
  const result = await riskyOperation();
  return { success: true, data: result };
} catch (error) {
  console.error('Operation failed:', error);
  return { success: false, error: error.message };
}
```

## Environment Variables

### Required Variables

**Security:**
- `HMAC_SECRET` - 64-character hex string for data integrity (generate with: `openssl rand -hex 32`)
- `SESSION_SECRET` - Secret for session encryption

**Algorand:**
- `VITE_APP_WALLET_ADDRESS` - Issuer wallet address
- `VITE_ALGORAND_NETWORK` - `testnet` or `mainnet`
- `ISSUER_PRIVATE_KEY` - Issuer private key (NEVER commit to version control)

**Git Information (Optional - for system attestation):**
- `VITE_COMMIT_HASH` - Git commit hash (auto-set during build)
- `VITE_GIT_REPO_OWNER` - GitHub repository owner (auto-set during build)
- `VITE_GIT_REPO_SLUG` - GitHub repository name (auto-set during build)
- Note: These are automatically captured by the build script in package.json

**Firebase:**
- `VITE_FIREBASE_API_KEY`
- `VITE_FIREBASE_AUTH_DOMAIN`
- `VITE_FIREBASE_PROJECT_ID`
- `VITE_FIREBASE_STORAGE_BUCKET`
- `VITE_FIREBASE_MESSAGING_SENDER_ID`
- `VITE_FIREBASE_APP_ID`
- `VITE_FIREBASE_MEASUREMENT_ID`

**Firebase Admin (Server-side):**
- `GOOGLE_CREDENTIALS_JSON` - Minified JSON string (for Vercel/serverless)
- `GOOGLE_APPLICATION_CREDENTIALS` - Path to credentials file (for local dev)

**AWS (Optional - for ID verification):**
- `AWS_ACCESS_KEY_ID`
- `AWS_SECRET_ACCESS_KEY`
- `AWS_REGION`

**Google Document AI (Optional - for fraud detection):**
- `GOOGLE_PROJECT_ID`
- `GOOGLE_DOCUMENT_AI_PROCESSOR_ID`

### Security Best Practices

1. **Never commit secrets** - Add `.env` to `.gitignore`
2. **Use different secrets** for development and production
3. **Rotate keys regularly** - Especially API keys and HMAC secrets
4. **Minimum permissions** - AWS/Google credentials should have least privilege
5. **Monitor access logs** - Track API key usage

## Common Development Tasks

### Running the Development Server

```bash
npm run dev
```

Runs on `http://localhost:5173`

### Running Tests

```bash
npm test                  # Run all tests
npm run test:watch       # Watch mode
npm run test:ui          # UI mode with browser
```

### Type Checking

```bash
npm run typecheck
```

### Building for Production

```bash
npm run build
```

### Starting Production Server

```bash
npm start
```

### Algorand Testing

```bash
npm run test:algorand           # General Algorand tests
npm run test:algorand:testnet   # Testnet tests
npm run test:algorand:localnet  # Local network tests
npm run test:algorand:wallet    # Wallet tests
npm run test:algorand:credential # Credential tests
```

### AWS Testing

```bash
npm run test:aws
```

### Setting up Local Algorand Network

```bash
npm run setup:localnet
npm run docker:localnet         # Start Docker container
npm run docker:localnet:stop    # Stop Docker container
```

### AlgoKit (Smart Contract Development)

```bash
npm run algokit:build           # Build contracts
npm run algokit:generate        # Generate TypeScript clients
```

## Testing

### Test Files

Tests use Vitest and are located alongside source files with `.test.ts` or `.test.tsx` extensions.

### Testing Verification Flow

See [docs/TESTING_VERIFICATION.md](docs/TESTING_VERIFICATION.md) for complete guide on testing the identity verification flow with mock providers.

**Mock Provider Server:**
```bash
node scripts/mock-provider-server.cjs
```

Runs on `http://localhost:3001` and simulates third-party identity verification.

### Mobile Client Testing

See [docs/MOBILE_CLIENT_TESTING.md](docs/MOBILE_CLIENT_TESTING.md) for integration testing with the React Native mobile wallet.

## Integration Guides

### Website Integration (Age Verification)

For websites wanting to verify user age using Cardless ID:

**Recommended: `@cardlessid/verify` browser SDK (no API key needed)**
```html
<script src="https://cdn.cardlessid.org/verify/latest/cardlessid-verify.js"></script>
<script>
  new CardlessIDVerify({
    minAge: 21,
    onVerified: (proof) => { /* allow access */ }
  }).mount('#age-gate');
</script>
```
The SDK handles nonce fetching, QR rendering, deep linking, and result polling automatically. See [sdk/INTEGRATION_GUIDE.md](sdk/INTEGRATION_GUIDE.md) for full guide.

**Legacy: API key challenge flow**

1. **Get API Key** - Contact team for integrator API key
2. **Create Challenge** - POST to `/api/integrator/challenge/create`
3. **Display QR Code** - Show returned QR code to user
4. **Poll for Response** - Check `/api/integrator/challenge/verify/:challengeId`
5. **Handle Result** - Allow/deny access based on response

See [docs/INTEGRATOR_README.md](docs/INTEGRATOR_README.md) for complete guide.

### Custom Identity Verification

For organizations wanting to issue their own credentials:

1. **Register as Issuer** - Register Algorand wallet address in issuer registry
2. **Implement Verification** - Build custom verification flow
3. **Issue Credentials** - Use Cardless ID credential schema
4. **Sign Credentials** - Create Ed25519 signature with issuer private key

See [docs/CUSTOM_VERIFICATION.md](docs/CUSTOM_VERIFICATION.md) for complete guide.

### Mobile Wallet Integration

For mobile app developers:

- **Wallet SDK:** See [Cardless Mobile](https://github.com/djscruggs/cardless-mobile) repository
- **Deep Linking:** See [docs/DEEP_LINKING.md](docs/DEEP_LINKING.md)
- **Credential Storage:** See [docs/WALLET_APP_GUIDE.md](docs/WALLET_APP_GUIDE.md)

## Security Considerations

### Data Privacy

1. **Transient Storage** - Identity data is never permanently stored
2. **HMAC Verification** - Data integrity without storing sensitive data
3. **Timing-Safe Comparisons** - Prevents timing attacks
4. **Automatic Deletion** - Photos deleted immediately after processing

### Cryptographic Security

1. **Ed25519 Signatures** - Industry-standard elliptic curve cryptography
2. **SHA-256 Hashing** - Composite hash for sybil resistance
3. **Secure Random Generation** - Cryptographically secure API keys

### API Security

1. **API Key Authentication** - Bearer token authentication for integrators
2. **Rate Limiting** - Prevent abuse (implementation varies by deployment)
3. **HTTPS Required** - All production traffic must use TLS
4. **CORS Configuration** - Restrict origins in production

### Blockchain Security

1. **Issuer Registry** - On-chain registry for issuer authorization
2. **Revocation Support** - Credentials can be revoked on-chain
3. **Proof Verification** - Cryptographic verification prevents forgery

## Documentation

### Available Docs

- **Integration:** [docs/INTEGRATOR_README.md](docs/INTEGRATOR_README.md)
- **Custom Verification:** [docs/CUSTOM_VERIFICATION.md](docs/CUSTOM_VERIFICATION.md)
- **Delegated Verification:** [docs/DELEGATED_VERIFICATION.md](docs/DELEGATED_VERIFICATION.md)
- **Smart Contract Architecture:** [app/routes/docs/smart-contracts.tsx](/docs/smart-contracts) - Algorand integration, NFT credential issuance, costs, and chain portability
- **Verification Protocol:** [app/routes/docs/verification-protocol.tsx](/docs/verification-protocol) - Stateless nonce-based flow, Algorand ed25519 proof signing, /api/v/* API reference, and SIOP-OID4VP standards compatibility
- **Deep Linking:** [docs/DEEP_LINKING.md](docs/DEEP_LINKING.md)
- **Firebase Setup:** [docs/FIREBASE-ADMIN-SETUP.md](docs/FIREBASE-ADMIN-SETUP.md)
- **Mobile Testing:** [docs/MOBILE_CLIENT_TESTING.md](docs/MOBILE_CLIENT_TESTING.md)
- **NFT Credentials:** [docs/NFT-CREDENTIAL-CLIENT-GUIDE.md](docs/NFT-CREDENTIAL-CLIENT-GUIDE.md)
- **Algorand:** [docs/README-ALGORAND.md](docs/README-ALGORAND.md)
- **Testing Verification:** [docs/TESTING_VERIFICATION.md](docs/TESTING_VERIFICATION.md)
- **Verification API:** [docs/VERIFICATION_API.md](docs/VERIFICATION_API.md)
- **VPN Risks:** [docs/VPN-AGE-VERIFICATION-RISKS.md](docs/VPN-AGE-VERIFICATION-RISKS.md)
- **Wallet Guide:** [docs/WALLET_APP_GUIDE.md](docs/WALLET_APP_GUIDE.md)

## Common Issues & Solutions

### Firebase Permission Denied

**Issue:** "Permission denied" when accessing Firebase
**Solution:** Check Firebase rules and ensure API keys are correct in `.env`

### Algorand Transaction Failed

**Issue:** Transaction fails with "insufficient balance"
**Solution:** Fund wallet with at least 0.2 ALGO for transaction fees

### ID Verification Fails

**Issue:** Document verification returns low confidence
**Solution:**
- Ensure good lighting and high-quality images
- Both sides of ID should be captured if required
- Check AWS Textract and Document AI credentials

### Face Match Low Confidence

**Issue:** Biometric verification fails
**Solution:**
- Ensure good lighting for selfie
- Face should be clearly visible, no sunglasses/masks
- Match ID photo as closely as possible

### HMAC Verification Error

**Issue:** "Invalid HMAC" error during credential creation
**Solution:** Verify `HMAC_SECRET` in `.env` matches between verification and credential creation

### BigInt Serialization Error

**Issue:** "TypeError: Do not know how to serialize a BigInt" during credential issuance
**Root Cause:** Algorand SDK returns BigInt values for asset IDs, transaction IDs, and numeric blockchain data. JavaScript's `Response.json()` cannot serialize BigInt directly.

**Symptoms:**
- Error occurs after successful NFT creation
- Only happens with full verification (not mock mode)
- Error message: `Credential issuance error: TypeError: Do not know how to serialize a BigInt`

**Solution:**
Use `JSON.stringify()` with a BigInt replacer function instead of `Response.json()`:

```typescript
// ❌ Wrong - fails with BigInt
return Response.json({
  assetId: BigInt(12345),
  duplicateAssetIds: [BigInt(111), BigInt(222)]
});

// ✅ Correct - use JSON.stringify with BigInt replacer
const responseData = {
  assetId: "12345",  // Convert to string
  duplicateAssetIds: [111, 222]  // Convert to numbers
};

const jsonString = JSON.stringify(responseData, (_key, value) =>
  typeof value === 'bigint' ? value.toString() : value
);

return new Response(jsonString, {
  status: 200,
  headers: { 'Content-Type': 'application/json' }
});
```

**Prevention:**
- Always convert BigInt to string or number before serialization
- Use `.toString()` or `Number()` for conversion
- Array conversion: `assetIds.map(id => Number(id))`
- See [docs/BIGINT_SERIALIZATION_FIX.md](docs/BIGINT_SERIALIZATION_FIX.md) for full details
- Tests: [app/__tests__/bigint-serialization.test.ts](app/__tests__/bigint-serialization.test.ts)

## Contributing

This is an open-source project welcoming contributions:

- **Documentation:** Improve guides and examples
- **Frontend:** Enhance UI/UX and accessibility
- **Backend:** Optimize verification providers
- **Testing:** Add test coverage
- **Mobile:** Contribute to React Native wallet

Contact: me@djscruggs.com or Telegram @djscruggs

## License

See [LICENSE.md](LICENSE.md) for details.

---

**Project Links:**
- **Main Repository:** https://github.com/djscruggs/cardless-id (assumed - update if different)
- **Mobile Wallet:** https://github.com/djscruggs/cardless-mobile
- **Website:** https://cardlessid.org (assumed - update if different)

**Last Updated:** 2026-04-14
**Version:** 1.0.0
~~~

## llms-full

Not found.