Employee Attendance & Visitor Management on the Stellar Blockchain
GateFlow is a workforce attendance and visitor access platform that combines a conventional, privacy-respecting application stack with tamper-evident, publicly verifiable audit trails anchored on the Stellar network using Soroban smart contracts.
Design principle: personal data stays off-chain; only cryptographic proofs go on-chain.
- Overview
- Why Stellar?
- Key Features
- Architecture
- Privacy and Data Protection Model
- Core Workflows
- Smart Contract Design
- Tech Stack
- Repository Structure
- Getting Started
- Configuration
- Database Schema
- API Reference
- Presence Verification and Anti-Fraud
- Identity, Wallets and Fees
- Testing
- Deployment
- Security
- Monitoring and Operations
- Roadmap
- Contributing
- FAQ
- License
Organizations need two things from their gates and front desks: trustworthy attendance records and controlled visitor access. Traditional systems store everything in a single database that administrators can quietly edit, and that auditors, clients or regulators have to take on trust.
GateFlow keeps the day-to-day experience simple (scan a QR code, tap a badge, receive a visitor pass) while adding a verifiable layer underneath:
- Employees check in and out via mobile app, QR, NFC or kiosk.
- Visitors are pre-registered by a host and receive a signed QR pass.
- Security guards scan passes at the gate and see instantly whether they are valid.
- HR and admins manage sites, schedules, reports and corrections.
- Auditors and third parties can independently verify that a record existed at a point in time and has not been altered, without ever seeing personal data.
- Companies with multiple sites, contractors or outsourced staff
- Estates, campuses, factories, hospitals and co-working spaces
- Organizations subject to audits, labour-compliance checks or client SLAs
- SaaS operators who want to offer customers verifiable records without asking them to trust the vendor
| Requirement | How Stellar helps |
|---|---|
| Low, predictable cost | Transaction fees are a tiny fraction of a cent, which makes anchoring hourly or daily practical |
| Fast finality | Ledgers close in roughly 5 seconds, so verification is near real time |
| Programmability | Soroban smart contracts (Rust compiled to WebAssembly) handle roles, passes and anchors |
| Sponsored fees | Fee-bump transactions let the company pay all fees, so users never touch crypto |
| Passkey wallets | Smart wallets secured by device biometrics remove key-management burden |
| Public verifiability | Anyone can inspect anchors via an explorer such as Stellar Expert |
Blockchain adds real value when you need tamper-proof audit trails, multi-party trust (contractors, clients, regulators) or a multi-tenant product where customers should not have to trust the operator. For a single company with none of those needs, a normal database is simpler. GateFlow keeps the on-chain footprint deliberately small so it earns its place.
- Employee check-in and check-out (QR, NFC, BLE/geofence, kiosk)
- Shift schedules, grace periods, late and early-leave rules
- Overtime and break tracking
- Leave and holiday calendars
- Manual corrections with a mandatory reason and approval workflow, recorded on-chain
- Periodic Merkle-root anchoring with per-record proof generation
- Exports: CSV, PDF, payroll-ready reports
- Host pre-registration with encrypted visitor details
- Signed, time-bound QR passes sent by email, SMS or WhatsApp
- Gate verification (valid, expired, revoked, wrong site)
- Entry and exit logging with host notifications
- Instant revocation and automatic expiry
- Walk-in registration at the kiosk with host approval
- Blacklist and watch-list support
- Visitor badge printing (optional)
- Role-based access control (admin, HR, security, host, auditor)
- Multi-site and (optionally) multi-tenant support
- Immutable audit log of all privileged actions
- Public proof-verification page for auditors
- Data-retention policies and subject-access/erasure tooling
- Webhooks and REST API for HRIS, payroll and access-control integrations
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Employee App │ │ Gate/Kiosk │ │ Admin Dashboard │
│ (React Native)│ │ App │ │ (Next.js) │
└──────┬───────┘ └──────┬───────┘ └────────┬─────────┘
│ │ │
└─────────────────┼───────────────────┘
▼
┌─────────────────────┐
│ Backend API │ NestJS · REST · WebSockets
│ Auth · RBAC · Logic │
└───┬────────┬────────┘
│ │
┌─────────▼──┐ ┌──▼───────────┐ ┌──────────────┐
│ PostgreSQL │ │ Redis/BullMQ │ │ KMS / Vault │
│ (PII, logs)│ │ (queues) │ │ (keys) │
└────────────┘ └──┬───────────┘ └──────────────┘
│
┌────────▼─────────┐
│ Anchoring Worker │ batches · Merkle trees · fee bumps
└────────┬─────────┘
▼
┌──────────────────┐
│ Soroban RPC │
└────────┬─────────┘
▼
┌──────────────────┐
│ Stellar Network │ GateFlow smart contracts
└──────────────────┘
| Layer | Responsibility |
|---|---|
| Client apps | UI, QR/NFC/BLE capture, device biometrics, offline queueing |
| Backend API | Authentication, authorization, business rules, encrypted PII handling, reporting, webhooks |
| PostgreSQL | Source of truth for personal data, schedules, raw attendance events, passes |
| Redis + BullMQ | Job queues, rate limiting, short-lived token cache |
| Anchoring worker | Builds Merkle trees, submits roots on-chain, stores proofs and transaction hashes |
| Soroban contracts | Roles, site registry, anchors, pass lifecycle, audit events |
| Stellar network | Consensus, timestamps, immutable history |
| Approach | Description | Trade-off |
|---|---|---|
| Per-event transactions | Every check-in or out is a contract call | Simple and transparent, but high volume and cost |
| Merkle anchoring (default) | Events are batched; only the root hash is written hourly or daily | Cheap and private; individual records proven via Merkle proof |
| Hybrid (recommended) | Merkle anchoring for routine events, direct on-chain calls for overrides, corrections and approvals | Best balance of cost and accountability |
Blockchain data is permanent and public. Nothing that identifies a person is ever written to the ledger.
- Merkle roots (32-byte hashes)
- Pass hashes (salted, non-reversible)
- Pass lifecycle events (issued, used, revoked) referencing hashes only
- Role assignments (organizational wallet addresses, not personal identities)
- Site identifiers (opaque IDs)
- Correction and override records (reason hash, approver address)
- Names, photos, phone numbers, emails, national IDs
- Raw attendance timestamps tied to a person
- Visitor company, purpose of visit and vehicle details
- Biometric templates (never stored by GateFlow; handled by the device)
Each Merkle leaf is computed as:
leaf = SHA-256( canonical_json({ employee_ref, site_id, type, timestamp, nonce }) || salt )
The per-record random salt prevents anyone from guessing employee IDs or timestamps by brute-forcing hashes. Salts are stored encrypted in the database and revealed only when a proof is shared deliberately.
Because personal data lives off-chain, a deletion request is honoured by deleting the database record and its salt. The remaining on-chain hash becomes unlinkable noise, which is the accepted approach for reconciling immutable ledgers with GDPR, NDPR and similar laws.
Disclaimer: This section describes a technical design, not legal advice. Consult counsel for your jurisdiction (GDPR, NDPR, POPIA, CCPA, etc.).
- Employee opens the app (or taps an NFC badge) at the gate.
- The app presents a rotating QR or reads the gate's rotating QR (see Section 14).
- The backend validates identity, shift, site and presence proof.
- The event is stored in PostgreSQL with a salted leaf hash.
- The employee receives instant confirmation.
- At the end of the period, the anchoring worker builds the Merkle tree and submits the root on-chain.
- A scheduled job selects all unanchored events for a
(site, period). - Leaves are sorted deterministically and the tree is built.
- The worker calls
anchor_attendance(site_id, period, merkle_root)through a fee-bump transaction. - The transaction hash and each record's Merkle proof are saved.
- The job is idempotent: retries never create duplicate anchors for the same
(site, period).
- The auditor receives a proof bundle:
{ leaf, proof[], root, period, site_id, tx_hash }. - They recompute the root from the leaf and proof.
- They compare it with the root stored in the contract (via the public verification page or Stellar Expert).
- A match proves the record existed at that time and has not been altered since.
Pre-register ──► Issue pass ──► Deliver QR ──► Gate scan ──► Entry ──► Exit
│ │ │ │
(host, off-chain) (on-chain pass) (verify_pass) (record_exit)
│ │
└────────── Revoke / Expire ─┘
- Pre-registration: a host enters visitor details; they are encrypted and stored off-chain.
- Pass issuance: the backend calls
issue_pass(host, pass_hash, valid_until, site_id). - Delivery: the visitor receives a signed QR code (Ed25519) by email, SMS or WhatsApp.
- Gate scan: the kiosk verifies the signature locally, then confirms status on-chain or from a cached, signed status feed.
- Entry and exit: logged off-chain; optionally mirrored on-chain via
record_entryandrecord_exit. - Revocation: a host or admin can revoke at any time; passes also expire automatically.
Corrections are privileged and always auditable:
- HR submits a correction with a reason.
- A second approver confirms (four-eyes principle).
- The backend writes a direct on-chain record containing the correction hash, approver and timestamp.
- The original event is never deleted; it is superseded by a linked correction record.
Contracts are written in Rust with the Soroban SDK.
| Contract | Purpose |
|---|---|
gateflow-core |
Roles, site registry, shared types, events |
gateflow-attendance |
Merkle-root anchors, corrections |
gateflow-visitors |
Pass issuance, entry/exit, revocation, verification |
A single combined contract is fine for the MVP; splitting makes upgrades and audits easier later.
| Role | Capabilities |
|---|---|
Admin |
Register sites, assign roles, pause the contract, upgrade |
HR |
Submit and approve corrections, anchor attendance |
Security |
Record entry/exit, verify passes |
Host |
Issue and revoke passes for own visitors |
Anchorer |
Service account allowed to submit Merkle roots |
| Function | Caller | Description |
|---|---|---|
initialize(admin) |
Deployer | One-time setup |
register_site(admin, site_id) |
Admin | Registers a site |
add_role(admin, address, role) |
Admin | Grants a role |
remove_role(admin, address, role) |
Admin | Revokes a role |
anchor_attendance(anchorer, site_id, period, merkle_root) |
Anchorer | Stores a root; rejects duplicates for the same (site, period) |
record_correction(hr, site_id, correction_hash, approver) |
HR | Logs an approved correction |
issue_pass(host, pass_hash, valid_until, site_id) |
Host | Creates a pass, returns pass_id |
record_entry(guard, pass_id) |
Security | Marks entry |
record_exit(guard, pass_id) |
Security | Marks exit |
revoke_pass(caller, pass_id) |
Host or Admin | Revokes a pass |
verify_pass(pass_id) |
Anyone (read-only) | Returns status: valid, expired, revoked, used |
get_anchor(site_id, period) |
Anyone (read-only) | Returns the stored root and ledger timestamp |
pause(admin) / unpause(admin) |
Admin | Emergency stop |
#[contracttype]
pub enum DataKey {
Admin,
Role(Address), // -> Role
Site(BytesN<32>), // -> bool (registered)
Anchor(BytesN<32>, u64), // (site_id, period) -> AnchorRecord
Pass(BytesN<32>), // pass_id -> PassRecord
Paused,
}
#[contracttype]
pub struct AnchorRecord {
pub merkle_root: BytesN<32>,
pub submitted_by: Address,
pub ledger_time: u64,
}
#[contracttype]
pub struct PassRecord {
pub pass_hash: BytesN<32>,
pub host: Address,
pub site_id: BytesN<32>,
pub valid_from: u64,
pub valid_until: u64,
pub status: PassStatus, // Issued | Entered | Exited | Revoked
}pub fn anchor_attendance(
env: Env,
anchorer: Address,
site_id: BytesN<32>,
period: u64,
merkle_root: BytesN<32>,
) {
anchorer.require_auth();
Self::assert_not_paused(&env);
Self::assert_role(&env, &anchorer, Role::Anchorer);
Self::assert_site_registered(&env, &site_id);
let key = DataKey::Anchor(site_id.clone(), period);
if env.storage().persistent().has(&key) {
panic_with_error!(&env, Error::AnchorAlreadyExists);
}
env.storage().persistent().set(&key, &AnchorRecord {
merkle_root: merkle_root.clone(),
submitted_by: anchorer,
ledger_time: env.ledger().timestamp(),
});
env.events().publish((symbol_short!("anchor"), site_id, period), merkle_root);
}- Soroban state has time-to-live (TTL); entries must be extended or they become archived. Anchors and passes should have their TTL extended on write, and a maintenance job should extend long-lived entries.
- Use
persistentstorage for anchors and passes,instancestorage for admin and pause flags, andtemporarystorage only for disposable data. - Keep records compact; storage costs scale with size and duration.
- Every state-changing function calls
require_auth()on the acting address. - Role checks are explicit and tested for every function.
- Anchors are write-once; corrections are append-only.
- An emergency
pauseis available to the admin (ideally a multisig). - Upgrades are restricted to the admin and should be time-locked in production.
- An independent audit is mandatory before mainnet.
| Component | Choice |
|---|---|
| Smart contracts | Rust + Soroban SDK |
| Tooling | Stellar CLI, cargo test, Soroban testutils |
| Network access | Soroban RPC (hosted provider or self-run) |
| JS integration | @stellar/stellar-sdk |
| Smart wallets | Stellar passkey-kit |
| Fees | Fee-bump transactions |
| Dev network | Testnet + Friendbot |
| Explorer | Stellar Expert |
| Component | Choice |
|---|---|
| Language / framework | TypeScript + NestJS |
| Database | PostgreSQL |
| ORM | Prisma or Drizzle |
| Cache and rate limiting | Redis |
| Job queue | BullMQ |
| Merkle trees | merkletreejs (or custom SHA-256) |
| Auth | JWT + refresh tokens; OIDC/SSO for enterprise |
| Encryption | AES-256-GCM for PII fields; keys in a KMS |
| QR signing | Ed25519, short-lived tokens |
| Notifications | SendGrid/Resend (email), Twilio/Termii (SMS), FCM/APNs (push), WhatsApp Business API |
| API style | REST + OpenAPI; WebSockets for live gate events |
| App | Choice |
|---|---|
| Admin dashboard | Next.js + Tailwind CSS + shadcn/ui |
| Employee app | React Native (Expo) |
| Gate/kiosk app | React Native or PWA on a tablet |
| QR scanning | expo-camera / react-native-vision-camera |
| NFC | react-native-nfc-manager |
| Charts | Recharts |
- Docker containers on AWS ECS, GCP Cloud Run or Kubernetes
- GitHub Actions for CI/CD
- Cloud secret manager and KMS/HSM for signing keys
- Sentry, Prometheus and Grafana for observability
- Automated PostgreSQL backups with point-in-time recovery
A pnpm + Turborepo monorepo keeps the contracts, API, web and mobile apps in sync.
gateflow/
├── apps/
│ ├── api/ # NestJS backend
│ │ ├── src/
│ │ │ ├── auth/
│ │ │ ├── employees/
│ │ │ ├── attendance/
│ │ │ ├── visitors/
│ │ │ ├── passes/
│ │ │ ├── sites/
│ │ │ ├── anchoring/ # Merkle builder + Stellar submitter
│ │ │ ├── notifications/
│ │ │ ├── audit/
│ │ │ └── common/
│ │ ├── prisma/
│ │ └── test/
│ ├── admin/ # Next.js admin dashboard
│ ├── mobile/ # React Native employee app (Expo)
│ ├── kiosk/ # Gate/kiosk app
│ └── verifier/ # Public proof-verification page
├── contracts/ # Soroban smart contracts (Rust workspace)
│ ├── gateflow-core/
│ ├── gateflow-attendance/
│ ├── gateflow-visitors/
│ └── Cargo.toml
├── packages/
│ ├── shared-types/ # TypeScript types shared across apps
│ ├── merkle/ # Canonical leaf hashing + proof helpers
│ ├── stellar-client/ # Typed contract bindings and tx helpers
│ └── config/ # ESLint, TSConfig, Prettier presets
├── infra/
│ ├── docker/
│ ├── terraform/
│ └── k8s/
├── docs/
│ ├── architecture.md
│ ├── threat-model.md
│ ├── api.md
│ └── runbooks/
├── scripts/ # Deploy, seed, key-generation helpers
├── .github/workflows/
├── docker-compose.yml
├── turbo.json
├── pnpm-workspace.yaml
└── README.md
| Tool | Version (minimum) | Purpose |
|---|---|---|
| Node.js | 20 LTS | Backend and frontends |
| pnpm | 9+ | Package management |
| Rust | stable (with wasm32v1-none or wasm32-unknown-unknown target) |
Contracts |
| Stellar CLI | latest | Build, deploy, invoke contracts |
| Docker + Docker Compose | latest | Local PostgreSQL and Redis |
| Expo CLI | latest | Mobile development |
Check the Stellar developer docs for the current recommended Rust target and CLI install method, as these change between releases.
git clone https://github.com/<your-org>/gateflow.git
cd gateflow
pnpm installdocker compose up -d # PostgreSQL + Redis
cp .env.example .env # then edit values (see Section 11)
pnpm --filter api prisma migrate dev
pnpm --filter api prisma db seed# 1. Create and fund a deployer identity on testnet
stellar keys generate gateflow-deployer --network testnet --fund
# 2. Build contracts
cd contracts
stellar contract build
# 3. Deploy
stellar contract deploy \
--wasm target/wasm32v1-none/release/gateflow_core.wasm \
--source gateflow-deployer \
--network testnet
# -> prints the contract ID; add it to .env as CORE_CONTRACT_ID
# 4. Initialize
stellar contract invoke \
--id <CORE_CONTRACT_ID> \
--source gateflow-deployer \
--network testnet \
-- initialize --admin <ADMIN_ADDRESS>The WASM output path depends on your Rust target. Adjust the path if you use
wasm32-unknown-unknown.
GateFlow uses two Stellar accounts:
| Account | Role |
|---|---|
| Fee sponsor | Pays all transaction fees through fee-bump transactions |
| Anchorer | Holds the Anchorer role and signs anchor_attendance calls |
stellar keys generate gateflow-sponsor --network testnet --fund
stellar keys generate gateflow-anchorer --network testnet --fundIn production, these keys live in a KMS/HSM and are never written to .env.
pnpm dev # everything via Turborepo
# or individually:
pnpm --filter api dev # http://localhost:3001
pnpm --filter admin dev # http://localhost:3000
pnpm --filter verifier dev # http://localhost:3002
pnpm --filter mobile start # Expo
pnpm --filter kiosk start- Create the first admin user (
pnpm --filter api cli create-admin) - Register a site and note its
site_id - Grant on-chain roles to the anchorer and security accounts
- Enrol a test employee and perform a check-in
- Trigger a manual anchoring run (
pnpm --filter api cli anchor --site <id>) - Verify the anchor on Stellar Expert (testnet)
Copy .env.example to .env and set the following.
| Variable | Description | Example |
|---|---|---|
NODE_ENV |
Runtime environment | development |
PORT |
API port | 3001 |
DATABASE_URL |
PostgreSQL connection string | postgresql://gateflow:gateflow@localhost:5432/gateflow |
REDIS_URL |
Redis connection string | redis://localhost:6379 |
| Variable | Description |
|---|---|
JWT_SECRET |
Access-token signing secret |
JWT_EXPIRES_IN |
Access-token lifetime (e.g. 15m) |
REFRESH_TOKEN_EXPIRES_IN |
Refresh-token lifetime (e.g. 7d) |
OIDC_ISSUER_URL |
Optional SSO provider |
| Variable | Description | Example |
|---|---|---|
STELLAR_NETWORK |
testnet or mainnet |
testnet |
STELLAR_NETWORK_PASSPHRASE |
Network passphrase | Test SDF Network ; September 2015 |
SOROBAN_RPC_URL |
Soroban RPC endpoint | https://soroban-testnet.stellar.org |
HORIZON_URL |
Horizon endpoint (if used) | https://horizon-testnet.stellar.org |
CORE_CONTRACT_ID |
Deployed core contract | C... |
ATTENDANCE_CONTRACT_ID |
Deployed attendance contract | C... |
VISITORS_CONTRACT_ID |
Deployed visitors contract | C... |
SPONSOR_SECRET |
Fee-sponsor key (dev only; use KMS in production) | S... |
ANCHORER_SECRET |
Anchorer key (dev only; use KMS in production) | S... |
KMS_KEY_ID |
KMS key reference for production signing |
| Variable | Description |
|---|---|
PII_ENCRYPTION_KEY_ID |
KMS key used to wrap data-encryption keys |
QR_SIGNING_PRIVATE_KEY |
Ed25519 key for signing visitor passes (KMS in production) |
QR_TOKEN_TTL_SECONDS |
Lifetime of rotating employee QR tokens (e.g. 30) |
| Variable | Description | Default |
|---|---|---|
ANCHOR_CRON |
Anchoring schedule | 0 * * * * (hourly) |
ANCHOR_PERIOD |
Period granularity: hourly or daily |
daily |
ANCHOR_MAX_RETRIES |
Retries before alerting | 5 |
| Variable | Description |
|---|---|
EMAIL_PROVIDER / EMAIL_API_KEY |
SendGrid or Resend |
SMS_PROVIDER / SMS_API_KEY |
Twilio or Termii |
FCM_SERVER_KEY |
Push notifications (Android) |
APNS_KEY_ID / APNS_TEAM_ID |
Push notifications (iOS) |
Never commit
.envfiles or secret keys..gitignorealready excludes them; keep it that way.
Core tables (simplified). Personally identifying columns are encrypted at the application layer.
| Table | Key columns | Notes |
|---|---|---|
tenants |
id, name, plan |
Only used in multi-tenant mode |
sites |
id, tenant_id, name, site_id_hash, timezone, geofence |
site_id_hash is the on-chain identifier |
users |
id, tenant_id, email_enc, role, password_hash, status |
Admin, HR, security, host |
employees |
id, tenant_id, employee_no, name_enc, phone_enc, department, shift_id |
PII encrypted |
devices |
id, employee_id, public_key, platform, bound_at |
Device binding for anti-fraud |
shifts |
id, name, start, end, grace_minutes |
|
attendance_events |
id, employee_id, site_id, type, occurred_at, method, leaf_hash, salt_enc, anchor_id |
type: IN / OUT |
anchors |
id, site_id, period, merkle_root, tx_hash, ledger, status, created_at |
|
merkle_proofs |
event_id, proof_json |
Stored for fast retrieval |
corrections |
id, event_id, reason, requested_by, approved_by, onchain_tx_hash |
Append-only |
visitors |
id, name_enc, phone_enc, email_enc, company_enc, purpose_enc |
PII encrypted |
passes |
id, visitor_id, host_id, site_id, pass_hash, valid_from, valid_until, status, onchain_tx_hash |
|
visits |
id, pass_id, entered_at, exited_at, guard_id |
|
audit_logs |
id, actor_id, action, entity, payload, created_at |
Append-only; hash-chained |
webhooks |
id, tenant_id, url, secret, events |
Base URL: /api/v1. Full OpenAPI documentation is served at /api/docs when the API is running.
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/login |
Email/password login |
| POST | /auth/refresh |
Refresh access token |
| POST | /auth/logout |
Invalidate refresh token |
| GET | /auth/me |
Current user |
| Method | Endpoint | Description | Roles |
|---|---|---|---|
| GET/POST | /employees |
List / create employees | Admin, HR |
| GET/PATCH/DELETE | /employees/:id |
Manage an employee | Admin, HR |
| POST | /employees/:id/devices |
Bind a device | Admin, HR |
| GET/POST | /sites |
List / register sites (also registers on-chain) | Admin |
| GET/POST | /shifts |
Manage shifts | Admin, HR |
| Method | Endpoint | Description | Roles |
|---|---|---|---|
| POST | /attendance/qr-token |
Get a short-lived rotating QR token | Employee |
| POST | /attendance/check-in |
Check in | Employee, Kiosk |
| POST | /attendance/check-out |
Check out | Employee, Kiosk |
| GET | /attendance |
Query events with filters | Admin, HR |
| POST | /attendance/corrections |
Request a correction | HR |
| POST | /attendance/corrections/:id/approve |
Approve (second approver) | Admin, HR |
| GET | /attendance/reports |
Daily/weekly/monthly reports | Admin, HR |
| GET | /attendance/events/:id/proof |
Merkle proof bundle for an event | Admin, HR, Auditor |
| Method | Endpoint | Description |
|---|---|---|
| GET | /anchors |
List anchors and statuses |
| POST | /anchors/run |
Trigger an anchoring run manually |
| GET | /anchors/:id |
Anchor details with tx hash and explorer link |
| POST | /verify/proof |
Public: verify a proof bundle against the chain |
| Method | Endpoint | Description | Roles |
|---|---|---|---|
| POST | /visitors |
Pre-register a visitor | Host |
| POST | /passes |
Issue a pass (on-chain) | Host |
| GET | /passes/:id |
Pass details | Host, Security |
| POST | /passes/:id/revoke |
Revoke a pass | Host, Admin |
| POST | /gate/verify |
Verify a scanned QR | Security, Kiosk |
| POST | /gate/entry |
Record entry | Security |
| POST | /gate/exit |
Record exit | Security |
| POST | /gate/walk-in |
Register a walk-in, request host approval | Kiosk |
Namespace /ws:
| Event | Payload |
|---|---|
gate.entry |
{ passId, siteId, time } |
gate.exit |
{ passId, siteId, time } |
pass.revoked |
{ passId } |
anchor.confirmed |
{ siteId, period, txHash } |
Outbound webhooks are signed with HMAC-SHA256 (X-GateFlow-Signature). Supported events include attendance.checked_in, attendance.checked_out, visitor.arrived, visitor.departed, pass.issued, pass.revoked and anchor.confirmed.
{
"statusCode": 403,
"error": "Forbidden",
"message": "Pass has been revoked",
"code": "PASS_REVOKED"
}Preventing "buddy punching" is as important as recording attendance. GateFlow supports layered checks that can be combined per site.
| Method | How it works | Strength |
|---|---|---|
| Rotating QR | Gate screen or the employee app shows a QR that changes every ~30 seconds; tokens are single-use and server-validated | Medium |
| NFC badge | Employee taps a badge or phone on a gate reader | Medium |
| BLE beacon | App detects a site beacon within range | Medium |
| Geofence | GPS check against the site boundary | Low to medium (spoofable) |
| Device binding | Each employee is bound to a registered device (public key) | Medium |
| Biometric unlock | Device Face ID/fingerprint gates the app action via passkeys | High |
| Kiosk with camera | Optional photo capture, stored encrypted and subject to retention limits | High |
Additional safeguards:
- Replay protection through single-use tokens and nonces
- Rate limiting and anomaly detection (impossible travel, duplicate check-ins)
- Offline mode on kiosks: signed events are queued locally and synced once connectivity returns, preserving original timestamps with a server-side sanity window
- Clear audit entries for every rejected attempt
Employees and visitors interact with a normal app. The blockchain is invisible to them.
| Option | Description | When to use |
|---|---|---|
| Backend-managed keys | The backend signs on behalf of organizational roles (anchorer, host service accounts) | MVP and most deployments |
| Passkey smart wallets | Individuals hold passkey-secured smart wallets (via passkey-kit) and approve actions with biometrics | When individual non-repudiation matters |
All fees are paid by the company's sponsor account using fee-bump transactions:
- The inner transaction is built and signed by the acting account (anchorer, host, etc.).
- The sponsor wraps it in a fee-bump transaction and signs.
- The wrapped transaction is submitted through Soroban RPC.
Monitor the sponsor account's XLM balance and alert when it falls below a configurable threshold.
- Signing keys in a KMS or HSM; the application requests signatures and never sees raw keys
- Separate keys per environment and per role
- Admin role held by a multisig account
- Documented key-rotation and recovery procedures
| Level | Tooling | What it covers |
|---|---|---|
| Contract unit tests | cargo test + Soroban testutils |
Every function, every role, every failure path |
| Contract property tests | proptest (Rust) |
Invariants such as write-once anchors, no pass reuse after revoke |
| Backend unit tests | Jest or Vitest | Services, Merkle logic, encryption helpers |
| Backend API tests | Supertest | Endpoints, RBAC, validation |
| Integration tests | Testcontainers + Stellar testnet/local network | Full anchoring and pass flows |
| Web E2E | Playwright | Admin dashboard and verifier page |
| Mobile E2E | Detox or Maestro | Check-in and kiosk flows |
| Load tests | k6 | Peak shift-change traffic at the gate |
# Contracts
cd contracts && cargo test
# Backend
pnpm --filter api test
pnpm --filter api test:e2e
# Everything
pnpm test
pnpm lint
pnpm typecheckThe packages/merkle package is shared by the backend and the verifier page, so a single test suite guarantees that canonical hashing, ordering and proof verification behave identically everywhere. Include fixtures with known roots, odd leaf counts, single-leaf trees and tampered-proof cases.
| Environment | Network | Purpose |
|---|---|---|
local |
Testnet or local Stellar quickstart | Development |
staging |
Testnet | Integration, UAT, demos |
production |
Mainnet | Live use |
docker build -t gateflow-api ./apps/api
docker build -t gateflow-admin ./apps/adminDeploy to ECS, Cloud Run or Kubernetes. Run the anchoring worker as a separate process from the API so that it can scale and restart independently, and ensure only one worker holds the anchoring lock per site at a time.
- Independent smart-contract security audit completed and findings resolved
- Admin role assigned to a multisig account
- All signing keys moved to KMS/HSM
- Sponsor account funded with low-balance alerts enabled
- Contract TTL-extension job deployed and monitored
- Database backups and point-in-time recovery tested with a restore drill
- Rate limits, WAF and TLS configured
- Penetration test completed
- Data-protection impact assessment and privacy notice published
- Runbooks written for key compromise, contract pause and RPC outage
- Rollback plan for backend and (where possible) contract upgrades
| Threat | Mitigation |
|---|---|
| Buddy punching / spoofed check-ins | Rotating tokens, device binding, biometrics, geofence/BLE |
| Forged or shared visitor QR codes | Ed25519 signatures, short validity, single-use entry, on-chain status check |
| Insider editing of attendance records | Merkle anchoring, append-only corrections, four-eyes approval |
| PII leakage | App-layer AES-256-GCM, KMS-wrapped keys, least-privilege access, no PII on-chain |
| Stolen signing keys | KMS/HSM, role separation, key rotation, multisig admin, pause switch |
| Contract bugs | Audit, tests, formal invariants, pause and time-locked upgrades |
| RPC provider outage | Multiple RPC endpoints, retry with backoff, anchoring queue with alerts |
| Replay attacks | Nonces, short TTLs, server-side token consumption |
| Denial of service | Rate limiting, WAF, queue back-pressure, offline kiosk mode |
| Sponsor account drain | Spending limits, allow-listed contracts and operations, balance alerts |
- Input validation on every endpoint; parameterized queries only
- Strict CORS, security headers and CSRF protection where applicable
- Password hashing with Argon2id; optional MFA for admin and HR roles
- Dependency scanning (Dependabot,
cargo audit,pnpm audit) in CI - Secrets scanning in CI to block committed keys
- Principle of least privilege for database roles and cloud IAM
Please do not open public issues for security problems. Email security@your-domain.example with details and reproduction steps. We aim to acknowledge reports within 72 hours.
| Concern | Tooling | Alert examples |
|---|---|---|
| Errors and traces | Sentry, OpenTelemetry | Spike in 5xx responses |
| Metrics | Prometheus + Grafana | Gate verification latency, check-in rate |
| Logs | Loki or CloudWatch (structured JSON) | Repeated failed token validations |
| Anchoring health | Custom metric + alert | No successful anchor within 2× the schedule |
| Sponsor balance | Scheduled check | XLM balance below threshold |
| Contract TTL | Scheduled job | Entries nearing archival |
| Database | Cloud monitoring | Replication lag, storage, slow queries |
- Failed or stuck anchoring job
- Soroban RPC outage or degraded provider
- Sponsor account low on funds
- Key compromise and rotation
- Emergency contract pause and unpause
- Restoring PostgreSQL from backup
- Handling a data-subject deletion request
- Monorepo, CI and local dev environment
- Employee enrolment and QR check-in/out
- Daily Merkle anchoring to Soroban testnet
- Admin dashboard (employees, sites, attendance, reports)
- Proof generation and verification
- Visitor pre-registration and pass issuance
- Signed QR delivery via email/SMS/WhatsApp
- Gate/kiosk scanner app with offline mode
- Revocation, expiry and host notifications
- Walk-in registration and approval
- Fee sponsorship with spending guards
- Full RBAC and audit log export
- Public auditor verification page
- NFC, BLE and device-binding presence checks
- Payroll and HRIS integrations, webhooks
- External contract audit and penetration test
- Mainnet deployment with multisig admin
- Multi-site and multi-tenant support
- SSO (OIDC/SAML) and SCIM provisioning
- Passkey smart wallets for individual non-repudiation
- Zero-knowledge proofs for attendance claims (for example, "attended at least 20 days") without revealing dates
- Contractor and client portals with scoped proof access
- Tokenized or verifiable credentials for site inductions and safety training
- Integration with turnstiles, boom barriers and door controllers
- Analytics: occupancy, peak times, visitor flow
Contributions are welcome.
- Fork the repository and create a branch:
git checkout -b feat/short-description - Follow the code style (ESLint, Prettier,
cargo fmt,cargo clippy) - Add or update tests for every change
- Use Conventional Commits (
feat:,fix:,docs:…) - Run
pnpm lint && pnpm testandcargo testbefore pushing - Open a pull request describing the change, motivation and testing performed
Changes to contracts/ require at least two reviewers, updated tests including failure paths, and a note on any storage-layout or upgrade implications.
Participation is governed by the Contributor Covenant. Be respectful and constructive.
Do employees need a crypto wallet or XLM? No. The company pays all fees via fee-bump transactions, and users interact with a normal app.
Is personal data stored on the blockchain? No. Only hashes and opaque identifiers are stored. Personal data stays in the encrypted off-chain database.
How does this comply with the right to erasure? Deleting the off-chain record and its salt makes the on-chain hash unlinkable to any person. Confirm the approach with your legal counsel.
What happens if the blockchain or RPC is unavailable? Check-ins and gate scans continue to work because they rely on the off-chain system. Anchoring jobs queue and retry, and alerts fire if anchoring falls behind.
Can records be edited? Raw events are never deleted. Corrections are appended, require approval, and are recorded on-chain, so the full history remains visible.
How much does it cost to run on-chain? With Merkle anchoring, one small transaction per site per period (for example, daily), plus pass-related calls. Costs are minimal, but you must also budget for contract storage TTL extensions.
Can I use it without blockchain? The anchoring and pass-status modules are pluggable. You can run GateFlow in "database-only" mode, though you lose independent verifiability.
Single company or SaaS?
Both are supported by design. Single-organization deployments use one tenant; SaaS deployments add the tenants layer and per-tenant contract roles or per-tenant contract instances.
Released under the MIT License
- Stellar Development Foundation and the Soroban community
- Stellar Developer Docs
- Open-source libraries used throughout the stack
GateFlow: verifiable attendance and access, without exposing personal data.