Machine Readiness
Stored receipt and evidence
16
55
0
0
0
Samples
No stored offer samples.
Samples
No stored action samples.
Samples
No stored product samples.
Document
# 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:
Document
# 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
Document
Not stored for this site.