GuildPass provides wallet-based membership and token-gated community infrastructure for the Web3 / EVM ecosystem.
This monorepo contains a runnable MVP backend and protocol foundation. It is intentionally not feature-complete, but is real, demoable, and extendable.
Part of the Adamantine-Guild project.
| Path | Purpose |
|---|---|
apps/access-api |
Fastify REST API (TypeScript, Prisma, PostgreSQL, OpenAPI) |
packages/contracts |
TypeScript helpers for on-chain contract addresses and ABIs |
packages/shared-types |
Shared types and enums for roles, membership, and decisions |
packages/policy-engine |
Simple, explainable access policy engine |
packages/sdk-lite |
Minimal HTTP client for the access API |
contracts/ |
Foundry Solidity project (MembershipNFT + tests + deploy scripts) |
- Node.js 18+
- npm 9+
- Docker (for PostgreSQL and Redis)
- Foundry (for Solidity contracts)
# 1. Clone and enter the repo
git clone https://github.com/Adamantine-Guild/guildpass-core.git
cd guildpass-core
# 2. Start PostgreSQL and Redis
docker compose up -d
# 3. Install dependencies
npm install
# 4. Set up environment variables
cp .env.example .env
# Edit .env — set DATABASE_URL, REDIS_URL, etc.
# 5. Generate Prisma client and run migrations
npm run -w access-api prisma:migrate
# 6. Seed the database with sample data
npm run seed
# 7. Start the API in development mode
npm run devOpenAPI docs available at: http://localhost:3000/docs
The MembershipNFT is a simple ERC-721 with expiry and suspension semantics, and admin-controlled mint/renew. It supports multi-community memberships, meaning a single deployed contract can represent memberships across multiple communities via the communityId mapping. Events emitted are suitable for off-chain indexing and include the associated communityId to easily map to the backend state.
# Build contracts
npm run contracts:build # runs: forge build
# Test contracts
npm run contracts:test # runs: forge test
# Deploy (example script)
npm run contracts:deploy # runs: forge script contracts/script/Deploy.s.sol --broadcastAfter deploying, set MEMBERSHIP_NFT_ADDRESS and CHAIN_ID in .env.
The GuildPass Access API follows a strict versioning and compatibility contract for all /v1 routes:
- Version Header: All API responses include an
x-guildpass-api-versionheader (e.g.,1.0.0) indicating the version being served. - Server Version: The
GET /health/liveendpoint exposes the current server API version. - Backwards Compatibility: We commit to maintaining backwards compatibility for all
/v1routes. We will not remove fields from responses or require new mandatory request parameters without bumping the major API version (e.g., to/v2). - Deprecation: If a
/v1route or field needs to be deprecated, we will serve adeprecation: trueheader on those responses and provide guidance in our documentation. Deprecated endpoints will continue to function for a minimum sunset period before removal. Clients are encouraged to monitor thedeprecationheader.
| Method | Path | Description |
|---|---|---|
| GET | /v1/memberships/:wallet |
Membership status summary by wallet |
| GET | /v1/members/:wallet |
Member profile (with membership and roles) |
| POST | /v1/access/check |
Access decision for { wallet, communityId, resource } |
| GET | /v1/communities/:communityId/members |
Admin member listing |
Responses include allowed/denied plus human-readable and machine-readable reasons.
A stable, machine-readable OpenAPI specification is generated for all public API routes to support SDKs and integrations.
- Specification File: docs/openapi.json
For Contributors: When adding or modifying routes in the Access API, you must update the checked-in specification. Run the following command from the root of the repository:
npm run -w access-api openapi:generateCI will automatically verify that the OpenAPI specification is up-to-date with your code changes.
Prisma schema includes: communities, wallets, members, memberships, roles, access policies, profiles, badges (placeholder), audit_events, and outbox_events.
The API uses the transactional outbox pattern to emit reliable integration events when domain state changes. Every mutation that affects memberships, roles, policies, resources, or access decisions writes a durable event to the OutboxEvent table within the same database transaction as the state change. This guarantees that no event is lost on request failure or process restart.
| Concept | Description |
|---|---|
| Event creation | Events are written atomically with the domain mutation inside a Prisma $transaction. If the mutation fails, no event is created. If the event write fails, the entire transaction rolls back. |
| Event types | MEMBERSHIP_CREATED, MEMBERSHIP_UPDATED, MEMBERSHIP_DELETED, ROLE_ASSIGNED, ROLE_REMOVED, RESOURCE_CREATED, RESOURCE_UPDATED, RESOURCE_ARCHIVED, POLICY_CREATED, POLICY_UPDATED, POLICY_DELETED, ACCESS_DECISION |
| Statuses | pending (awaiting delivery), delivered (successfully processed), failed (permanently failed after max retries) |
| Retry strategy | Exponential backoff: nextRetryAt = now + 10 × 2^retryCount seconds. Default max 5 retries. |
| Delivery worker | outboxWorker polls for pending events every OUTBOX_WORKER_INTERVAL_MS (default 10s) and delegates to a pluggable handler. The default handler is a no-op logger. |
| Pruning | Delivered events older than 7 days are automatically pruned to prevent unbounded table growth. |
| Environment Variable | Default | Description |
|---|---|---|
OUTBOX_WORKER_INTERVAL_MS |
10000 |
Polling interval for the outbox worker (ms) |
OUTBOX_WORKER_BATCH_SIZE |
50 |
Max events processed per worker pass |
The outbox worker accepts a custom OutboxEventHandler function. Replace the default no-op with your own delivery logic (HTTP webhook, NATS, Kafka, analytics pipeline, etc.):
import { createOutboxWorker, OutboxEventHandler } from './workers/outboxWorker';
const myHandler: OutboxEventHandler = async (event) => {
await fetch('https://hooks.example.com/integration', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event),
});
};
const worker = createOutboxWorker(10_000, myHandler);
worker.start();| Metric | Type | Labels |
|---|---|---|
outbox_events_created_total |
Counter | event_type |
outbox_events_delivered_total |
Counter | event_type |
outbox_events_failed_total |
Counter | event_type |
Simple rules: PUBLIC, MEMBERS_ONLY, ADMINS_ONLY, CONTRIBUTORS_OR_ADMINS.
Role resolution combines:
- Membership state (adds
memberrole when active) - Backend role assignments
- Room for future manual override rules (TODO)
# All tests across workspaces
npm run test
# Policy engine unit tests
npm run -w @guildpass/policy-engine test
# Access API unit and integration tests
npm run -w access-api test
# Contract tests (Foundry)
npm run contracts:test
# TypeScript type checking
npm run typecheckWhen you change the Prisma schema or migration history, validate the database workflow locally before opening a pull request:
pnpm install --frozen-lockfile
createdb guildpass_test
createdb guildpass_shadow
DATABASE_URL=postgresql://localhost:5432/guildpass_test \
SHADOW_DATABASE_URL=postgresql://localhost:5432/guildpass_shadow \
pnpm --filter access-api prisma:validate
pnpm --filter access-api prisma:generate
pnpm --filter access-api prisma:migrate:deploy
pnpm --filter access-api prisma:migrate:checkThe CI workflow runs the same validation steps against a disposable PostgreSQL service and a shadow database so drift is caught before merge.
The Membership Integration Test (apps/access-api/src/membership-integration.test.ts) validates the complete flow from MembershipNFT contract events to API access decisions:
- Contract Events → Database State → Policy Engine → API Response
- Tests event ingestion (MembershipMinted, MembershipRenewed, MembershipSuspended)
- Validates active, expired, and suspended membership scenarios
- Proves access control decisions reflect actual membership state
- Can run locally without a live blockchain
See apps/access-api/INTEGRATION_TEST_GUIDE.md for detailed documentation.
This project uses ESLint to maintain code quality.
- Run linting for all packages:
npm run lint - Run linting for a specific package:
npm run lint -w <package-name>
See .env.example for all required variables.
- Advanced governance permissions
- Constitutional rule engine
- Complex moderation workflows / appeals / reinstatement
- Rich reward distribution and advanced streak logic
- Contribution scoring engine
- Full event attendance ingestion
- Multi-chain support (current: EVM only)
- Advanced indexing pipeline
Clear interfaces and TODOs are left where appropriate.
- Business logic lives in services and the policy engine, not route handlers.
- Contracts and API are aligned via shared types and simple event ABI.
- The code aims to be small and understandable; extending should not require rewrites.
We welcome contributions! See CONTRIBUTING.md for the full guide.
- Browse open issues tagged
good first issueorhelp wanted. - Comment directly on the GitHub issue you'd like to work on.
- Fork the repo, create a feature branch, implement your change, open a PR.
- Contact: cerealboxx123@gmail.com
MIT — see LICENSE.