{"id":44175845,"url":"https://github.com/edjCase/motoko_atproto_pds","last_synced_at":"2026-02-21T13:01:10.666Z","repository":{"id":324270888,"uuid":"1096069334","full_name":"edjCase/motoko_atproto_pds","owner":"edjCase","description":"PDS canister for AT Protocol written in Motoko for the Internet Computer","archived":false,"fork":false,"pushed_at":"2026-01-01T19:10:51.000Z","size":238,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-07T02:35:08.933Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Motoko","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edjCase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-13T22:41:19.000Z","updated_at":"2026-01-01T19:10:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/edjCase/motoko_atproto_pds","commit_stats":null,"previous_names":["edjcase/motoko_atproto_pds"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/edjCase/motoko_atproto_pds","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_atproto_pds","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_atproto_pds/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_atproto_pds/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_atproto_pds/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edjCase","download_url":"https://codeload.github.com/edjCase/motoko_atproto_pds/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_atproto_pds/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29681468,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T12:30:22.644Z","status":"ssl_error","status_checked_at":"2026-02-21T12:29:55.402Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-09T13:00:34.292Z","updated_at":"2026-02-21T13:01:10.659Z","avatar_url":"https://github.com/edjCase.png","language":"Motoko","funding_links":[],"categories":["Social Applications"],"sub_categories":["Protocols"],"readme":"# AT Protocol PDS for Internet Computer\n\nA decentralized Personal Data Server (PDS) implementation for the [AT Protocol](https://atproto.com/) (used by Bluesky and other decentralized social networks) built in Motoko for the Internet Computer blockchain.\n\n## Overview\n\nThis project provides a canister-based PDS that enables DAOs (Decentralized Autonomous Organizations) to create and manage their own AT Protocol identities and post to Bluesky and other AT Protocol networks. Unlike traditional PDS implementations designed for individual users, this is specifically architected for organizational use where a DAO collectively controls the identity and content.\n\n### Key Features\n\n- **Full AT Protocol Implementation**: Complete support for the AT Protocol repository operations, including creating, updating, and deleting records\n- **DAO-Centric Design**: Purpose-built for DAOs to collectively manage social media presence\n- **ICRC-120 Support**: Automated deployment and management using the ICRC-120 standard\n- **Flexible Permission System**: Direct control or delegated permissions to other entities\n- **Certified Assets**: Secure serving of DID documents and well-known files\n- **Repository Management**: Full IPLD/CAR-based repository with commit history\n\n## Architecture\n\nThe PDS canister implements the following AT Protocol endpoints:\n\n### XRPC Endpoints\n- `com.atproto.repo.*` - Repository operations (create, put, delete, list records)\n- `com.atproto.sync.*` - Sync operations (getRepo, listBlobs, listRepos)\n- `com.atproto.identity.*` - Identity resolution\n- `app.bsky.actor.*` - Actor profile operations\n\n### Well-Known Routes\n- `/.well-known/did.json` - DID document for identity verification\n- `/xrpc/com.atproto.server.describeServer` - Server information\n\n## Known Limitations \u0026 Workarounds\n\n### WebSocket Requirement\n\n**Issue**: AT Protocol relays require a WebSocket connection via `com.atproto.sync.subscribeRepos` to crawl and index PDS servers. The Internet Computer does not natively support WebSocket connections.\n\n**Solution**: A reverse proxy architecture that routes WebSocket requests separately from regular HTTP requests.\n\n#### Architecture Overview\n\n```\nAT Protocol Relay\n       ↓\nCustom Domain (e.g., pds.edjcase.com)\n       ↓\nReverse Proxy (Cloudflare Worker)\n       ↓\n       ├─→ WebSocket Server (/xrpc/com.atproto.sync.subscribeRepos)\n       │   └─→ Polls PDS Canister for events\n       │\n       └─→ PDS Canister (all other requests)\n           └─→ IC Gateway ({canisterId}.raw.icp0.io)\n```\n\n#### Important: Domain Registration\n\n⚠️ **Do NOT register your custom domain with DNS directly to the Internet Computer.** The reverse proxy must handle the domain routing, otherwise WebSocket connections will fail. Configure your DNS to point to the reverse proxy instead.\n\n#### Implementation Steps\n\n1. **Deploy the WebSocket Server**\n   \n   Use the reference implementation: **[atproto_pds_ws_server](https://github.com/edjCase/atproto_pds_ws_server/)**\n   \n   This Node.js application polls your PDS canister for new events and serves them over WebSocket connections.\n\n2. **Configure Your Custom Domain**\n   \n   Point your custom domain (e.g., `pds.edjcase.com`) to your reverse proxy service (like Cloudflare).\n\n3. **Set Up the Reverse Proxy**\n   \n   Example using **Cloudflare Workers**:\n   \n   - Create a Cloudflare Worker\n   - Add a route for your domain: `pds.edjcase.com/*`\n   - Deploy the following worker code:\n\n   ```javascript\n   export default {\n     async fetch(request, env) {\n       const url = new URL(request.url);\n       \n       // Special WebSocket path - route to WebSocket server\n       if (url.pathname === '/xrpc/com.atproto.sync.subscribeRepos') {\n         const targetUrl = 'https://{websocketServerUrl}' + url.pathname + url.search;\n         return fetch(targetUrl, {\n           method: request.method,\n           headers: request.headers,\n           body: request.body,\n         });\n       }\n       \n       // All other paths - route to IC gateway\n       const icUrl = 'https://{canisterId}.raw.icp0.io' + url.pathname + url.search;\n       \n       return fetch(icUrl, {\n         method: request.method,\n         headers: request.headers,\n         body: request.body,\n         redirect: 'follow'\n       });\n     }\n   }\n   ```\n   \n   **Replace the placeholders**:\n   - `{websocketServerUrl}` - URL of your deployed WebSocket server\n   - `{canisterId}` - Your PDS canister ID\n\n4. **Configure Your PDS**\n   \n   When initializing your PDS canister, use your custom domain (e.g., `pds.edjcase.com`) as the hostname.\n\n#### How It Works\n\n1. **Regular Requests**: All standard AT Protocol requests go directly to your PDS canister via the IC gateway\n2. **WebSocket Requests**: The `com.atproto.sync.subscribeRepos` endpoint is intercepted and routed to the WebSocket server\n3. **Event Polling**: The WebSocket server continuously polls the PDS canister for new repository events\n4. **Event Streaming**: When new events occur, they're pushed through the WebSocket connection to the AT Protocol relay\n\nThis architecture bridges the gap between the Internet Computer's HTTP-only interface and the AT Protocol's WebSocket requirements, enabling full relay crawling and indexing support.\n\n## Example DAO Implementation\n\nThe [`examples/dao`](examples/dao) directory contains a complete reference implementation of a DAO that controls a PDS instance.\n\n### Features\n\n- **ICRC-120 Compatible**: Automated canister deployment and management\n- **Proposal-Based Governance**: All actions require DAO member approval\n- **Flexible Permissions**: Delegate specific permissions to entities for automated operations\n- **Multiple Proposal Types**:\n  - Post to Bluesky\n  - Install/upgrade PDS canister\n  - Set delegate permissions\n  - Set PDS canister reference\n  - Custom calls for extensibility\n\n### Deployment\n\nThe DAO example uses a proposal system where members can:\n1. Create proposals for actions (posting, configuration changes, etc.)\n2. Vote on proposals using their voting power\n3. Execute adopted proposals automatically\n\nSee the [DAO README](examples/dao/README.md) for detailed setup instructions.\n\n## Getting Started\n\n### Prerequisites\n\n- [DFX](https://internetcomputer.org/docs/current/developer-docs/setup/install) (Internet Computer SDK)\n- [Mops](https://mops.one/) (Motoko package manager)\n- Node.js (for the WebSocket proxy server)\n\n1. Clone the repository:\n```bash\ngit clone https://github.com/edjCase/motoko_atproto_pds.git\ncd motoko_atproto_pds\n```\n\n2. Install dependencies:\n```bash\nmops install\n```\n\n### Deploying to Local\n\n\n1. Start the local replica:\n```bash\ndfx start --background\n```\n\n2. Deploy the PDS:\n```bash\n# For local deployment with a new DID\n./scripts/deploy_local.sh new\n\n# For local deployment with an existing PLC DID\n./scripts/deploy_local.sh did:plc:your_existing_did\n```\n\nThe deployment scripts automatically:\n- Create or use existing canister\n- Generate or use the specified PLC DID\n- Configure the hostname based on the network and parameters\n- Initialize the PDS with the correct parameters\n\n### Configuration\n\nThe PDS initialization requires the following parameters (handled automatically by `deploy.sh`):\n\n```motoko\n{\n  plcKind: PlcKind;       // PLC directory configuration (new or existing DID)\n  hostname: Text;          // Your PDS hostname\n  serviceSubdomain: ?Text; // Optional subdomain for the service\n  owner: ?Principal;       // Optional owner principal (defaults to deployer)\n}\n```\n\n\n## Deploying to Mainnet\n\nThis section provides a comprehensive, step-by-step guide for deploying your PDS to the Internet Computer mainnet and making it accessible via a custom domain.\n\n### Step 1: Deploy the PDS Canister\n\nChoose one of the following deployment methods:\n\n#### Option 1: Via CLI (Direct Deployment)\n\n\u003e ✅ **Easiest method** - Quick deployment with minimal setup\n\u003e \n\u003e ⚠️ **Note**: If deploying for a DAO, you'll need to manually transfer ownership to the DAO canister after deployment using `setOwner()`.\n\nDeploy the PDS canister directly using the deployment script:\n\n```bash\n./scripts/deploy_ic.sh new {domain}\n\n# For IC deployment with a new DID\n./scripts/deploy_ic.sh new {domain}\n\n# For IC deployment with custom subdomain\n./scripts/deploy_ic.sh new {domain} auto {subdomain}\n\n# For IC deployment with an existing PLC DID\n./scripts/deploy_ic.sh {your_existing_plc_did} {domain}\n```\n\nReplace the placeholders:\n- `{domain}`: Your desired domain (e.g., `example.com`)\n- `{subdomain}`: Your desired subdomain (e.g., `pds`)\n- `{your_existing_plc_did}`: Your existing PLC DID (e.g., `did:plc:abcd1234`)\n\n#### Option 2: Via DAO (Governed Deployment)\n\n\u003e ✅ **Best for trustless DAO governance** - The PDS is owned by the DAO from deployment, eliminating the need for manual ownership transfer\n\u003e \n\u003e ⚠️ **More complex** - Requires ICRC-120 orchestrator setup and proposal-based deployment\n\nTo deploy via a DAO governance system, the DAO must make the following inter-canister calls upon proposal adoption:\n\n1. **Deploy the canister using ICRC-120 orchestrator**:\n   For more information on ICRC-120 deployment, see: **[icrc120.mo](https://github.com/icdevsorg/icrc120.mo/)**\n   ```motoko\n   // Call the ICRC-120 orchestrator\n   let results = await orchestrator.icrc120_upgrade_to(\n     daoPrincipal,\n     [{\n       canister_id = pdsCanisterId;  // Or create new via management canister\n       hash = wasmHash;               // SHA256 of PDS WASM module\n       args = initArgs;               // Encoded: { plcKind; hostname; serviceSubdomain; owner }\n       stop = true;\n       restart = true;\n       snapshot = false;\n       timeout = 600_000_000_000;    // 10 minutes\n       mode = #install;               // Or #reinstall, #upgrade\n       parameters = null;\n     }]\n   );\n   ```\n   \n   ⚠️ **Note**: ICRC-120 must be used for PDS deployment because the PDS WASM module exceeds 2MB, which is larger than the Internet Computer's management canister message size limit. ICRC-120 handles chunked WASM deployment automatically.\n   \n   **WASM Storage**: The [DAO example](examples/dao/src/backend/WasmStore.mo) uses local WASM storage where chunks are uploaded to the DAO canister. Alternatively, WASM modules can be retrieved from an ICRC-118 registry (not implemented in this example).\n   \n\n2. **Initialization arguments** must be encoded as Candid:\n   ```motoko\n   {\n     plcKind : { #new } or { #existing : Text };  // DID configuration\n     hostname : Text;                              // e.g., \"example.com\"\n     serviceSubdomain : ?Text;                     // e.g., ?\"pds\" for pds.example.com\n     owner : ?Principal;                           // Optional owner, defaults to deployer\n   }\n   ```\n\n3. **After deployment**, the DAO can interact with the PDS via its public API:\n   - `createRecord()` - Create posts/records\n   - `setDelegatePermissions()` - Grant permissions to other entities\n   - `setOwner()` - Transfer ownership\n   - Other [PDS API methods](#api-reference)\n\n**Reference Implementation**: See [examples/dao](examples/dao) for a complete DAO with PDS deployment proposals.\n\n### Step 2: Access Your PDS and Retrieve DID\n\n1. **Wait for initialization**\n   \n   After deployment, wait approximately 30 seconds for the canister to fully initialize.\n\n2. **Visit the PDS interface**\n   \n   Navigate to `https://{canister_id}.raw.icp0.io/` in your browser.\n   \n   You should see your PDS's interface.\n\n3. **Copy the PLC DID**\n   \n   From the UI, copy your PLC DID (it will look like `did:plc:...`). You'll need this for DNS configuration.\n\n### Step 3: Configure Custom Domain\n\nConfigure your DNS records to point your custom domain to the PDS. All settings below assume no subdomain; if using a subdomain (e.g., `pds.example.com`), modify each record accordingly.\n\n#### DNS Records\n\nAdd the following DNS records to your domain:\n\n1. **TXT Record for AT Protocol** (Optional)\n   ```\n   Record Type: TXT\n   Name: _atproto\n   Value: did={plc_did}\n   ```\n   Replace `{plc_did}` with the DID you copied from the UI.\n   \n   ⚠️ **Note**: This TXT record is optional. Your PDS will function without it, but adding it helps with identity verification in the AT Protocol network.\n   \n\n### Step 4: Set Up WebSocket Server\n\nThe AT Protocol requires WebSocket support for repository subscriptions. Deploy a WebSocket server to handle these connections.\n\nFor detailed information on why this is necessary, see the [WebSocket Requirement](#websocket-requirement) section.\n\n1. **Deploy the WebSocket server**\n   \n   Use the reference implementation: **[atproto_pds_ws_server](https://github.com/edjCase/atproto_pds_ws_server/)**\n\n2. **Configure environment variables**\n   ```\n   DOMAIN={PDS_DOMAIN}\n   ```\n   Replace `{PDS_DOMAIN}` with your PDS domain (e.g., `pds.example.com`).\n\n### Step 5: Set Up Reverse Proxy\n\nConfigure a reverse proxy to route traffic between your custom domain, the PDS canister, and the WebSocket server.\n\n#### Option A: Cloudflare\n\n1. **Add DNS Record**\n   ```\n   Record Type: CNAME\n   Name: @\n   Value: {domain}.icp1.io\n   Proxy Status: Proxied (orange cloud enabled)\n   ```\n   Replace `{domain}` with your domain name.\n\n2. **Create a Cloudflare Worker**\n   \n   - Navigate to Workers \u0026 Pages in your Cloudflare dashboard\n   - Click \"Create Application\" → \"Create Worker\"\n   - Replace the default code with the following:\n\n   ```javascript\n   export default {\n     async fetch(request, env) {\n       const url = new URL(request.url);\n       \n       // Special WebSocket path\n       if (url.pathname === '/xrpc/com.atproto.sync.subscribeRepos') {\n         const targetUrl = 'https://{websocketServerUrl}' + url.pathname + url.search;\n         return fetch(targetUrl, {\n           method: request.method,\n           headers: request.headers,\n           body: request.body,\n         });\n       }\n       \n       // All other paths -\u003e IC gateway\n       const icUrl = 'https://{pdsCanisterId}.raw.icp0.io' + url.pathname + url.search;\n       \n       return fetch(icUrl, {\n         method: request.method,\n         headers: request.headers,\n         body: request.body,\n         redirect: 'follow'\n       });\n     }\n   }\n   ```\n\n3. **Replace placeholders**\n   - `{websocketServerUrl}`: Your deployed WebSocket server URL (e.g., `ws.example.com`)\n   - `{pdsCanisterId}`: Your PDS canister ID from the deployment step\n\n4. **Add Worker Route**\n   \n   - Go to your website's Workers Routes settings\n   - Add a route: `{domain}/*`\n   - Select the worker you just created\n   \n   Replace `{domain}` with your domain name (e.g., `example.com`).\n\n#### Option B: Other Reverse Proxies\n\nIf using another reverse proxy solution (Nginx, Apache, etc.), configure it to:\n- Route all requests to `https://{pdsCanisterId}.raw.icp0.io`\n- **Except** requests to `/xrpc/com.atproto.sync.subscribeRepos`, which should route to your WebSocket server\n\nReplace `{pdsCanisterId}` with your PDS canister ID from the deployment step.\n\n### Step 6: Create Your First Post\n\nOnce everything is configured, test your PDS by creating a post:\n\n#### Option A: Via CLI (Direct)\n\n```bash\n./scripts/post_to_feed.sh ic \"Hello from my Internet Computer PDS!\"\n```\n\n#### Option B: Via DAO (Proposal)\n\nCreate a proposal to post to the feed through your DAO's governance system. See the [DAO example documentation](examples/dao/README.md) for details on creating and executing post proposals.\n\n### Step 7: Request Relay Crawling\n\nTo have your PDS indexed by the Bluesky network, you have two options:\n\n#### Option A: Via PDS Landing Page\n\nNavigate to your PDS landing page at `https://{your-domain.com}/` and use the built-in UI to request a crawl from the Bluesky relay.\n\n#### Option B: Via Script\n\n```bash\n./scripts/request_crawl.sh\n```\n\n\n⚠️ **Important**: Ensure your WebSocket server and reverse proxy are properly configured before requesting a crawl, or the relay will fail to index your PDS.\n\n## Utility Scripts\n\nThe `scripts/` directory contains helpful utilities for managing your PDS:\n\n### `deploy_local.sh`\n\nDeploys the PDS canister to the local network with initialization parameters.\n\n```bash\n./scripts/deploy_local.sh \u003cplc_did\u003e [mode]\n```\n\n**Arguments:**\n- `\u003cplc_did\u003e`: Either `new` to create a new DID, or an existing `did:plc:...` identifier\n- `[mode]`: Optional deployment mode (`auto`, `install`, `reinstall`, or `upgrade`)\n\n**Examples:**\n```bash\n# Deploy locally with a new DID\n./scripts/deploy_local.sh new\n\n# Deploy locally with an existing DID\n./scripts/deploy_local.sh did:plc:abcd1234\n\n# Reinstall on local network\n./scripts/deploy_local.sh new reinstall\n```\n\n### `deploy_ic.sh`\n\nDeploys the PDS canister to the Internet Computer mainnet with initialization parameters.\n\n```bash\n./scripts/deploy_ic.sh \u003cplc_did\u003e \u003chostname\u003e [mode] [serviceSubdomain]\n```\n\n**Arguments:**\n- `\u003cplc_did\u003e`: Either `new` to create a new DID, or an existing `did:plc:...` identifier\n- `\u003chostname\u003e`: Required. The base hostname (e.g., `example.com`)\n- `[mode]`: Optional deployment mode (`auto`, `install`, `reinstall`, or `upgrade`)\n- `[serviceSubdomain]`: Optional. The service subdomain (e.g., `pds`). If empty/null, uses only the hostname\n\n**Examples:**\n```bash\n# Deploy to IC with a new DID and subdomain\n./scripts/deploy_ic.sh new example.com auto pds\n# Results in: pds.example.com\n\n# Deploy to IC without subdomain\n./scripts/deploy_ic.sh new example.com\n# Results in: example.com\n\n# Deploy with an existing DID\n./scripts/deploy_ic.sh did:plc:abcd1234 example.com auto myservice\n\n# Reinstall with upgrade mode\n./scripts/deploy_ic.sh new example.com reinstall pds\n```\n\n### `post_to_feed.sh`\n\nCreate a Bluesky post directly from the command line.\n\n```bash\n./scripts/post_to_feed.sh \u003cnetwork\u003e \u003cmessage\u003e\n```\n\n**Arguments:**\n- `\u003cnetwork\u003e`: Target network (`local` or `ic`)\n- `\u003cmessage\u003e`: The text content of your post\n\n**Example:**\n```bash\n./scripts/post_to_feed.sh local \"Hello from the Internet Computer!\"\n```\n\n### `request_crawl.sh`\n\nRequest the Bluesky relay to crawl your PDS for indexing.\n\n```bash\n./scripts/request_crawl.sh\n```\n\nThis script sends a crawl request to `bsky.network` for the hostname configured in the script. Edit the `hostname` variable in the script to match your PDS domain.\n\n**Note:** Your PDS must have the WebSocket proxy properly configured for crawling to succeed.\n\n## API Reference\n\n### Repository Operations\n\n#### Create Record\n```motoko\ncreateRecord(request: CreateRecordRequest) -\u003e Result\u003cCreateRecordResponse, Text\u003e\n```\n\n#### Put Record\n```motoko\nputRecord(request: PutRecordRequest) -\u003e Result\u003cPutRecordResponse, Text\u003e\n```\n\n#### Delete Record\n```motoko\ndeleteRecord(request: DeleteRecordRequest) -\u003e Result\u003cDeleteRecordResponse, Text\u003e\n```\n\n#### Get Record\n```motoko\ngetRecord(request: GetRecordRequest) -\u003e Result\u003cGetRecordResponse, Text\u003e\n```\n\n#### List Records\n```motoko\nlistRecords(request: ListRecordsRequest) -\u003e Result\u003cListRecordsResponse, Text\u003e\n```\n\n### Permission Management\n\n#### Set Owner\n```motoko\nsetOwner(newOwner: Principal) -\u003e Result\u003c(), Text\u003e\n```\n\n#### Set Delegate Permissions\n```motoko\nsetDelegatePermissions(entity: Principal, permissions: Permissions) -\u003e Result\u003c(), Text\u003e\n```\n\nPermissions include:\n- `readLogs`: Read server logs\n- `deleteLogs`: Clear server logs\n- `createRecord`: Create new records\n- `putRecord`: Update existing records\n- `deleteRecord`: Delete records\n- `modifyOwner`: Change ownership\n\n## Project Structure\n\n```\nsrc/\n├── main.mo                         # Main canister entry point\n├── PdsInterface.mo                 # Public API interface\n├── XrpcRouter.mo                   # XRPC endpoint routing\n├── RestApiRouter.mo                # REST API routing\n├── WellKnownRouter.mo              # .well-known routes\n├── HtmlRouter.mo                   # HTML interface routes\n├── DID.mo                          # DID document handling\n├── ServerInfo.mo                   # Server information\n├── CarUtil.mo                      # CAR file utilities\n└── Handlers/\n    ├── RepositoryHandler.mo        # Repository operations\n    ├── RepositoryMessageHandler.mo # Event handling\n    ├── KeyHandler.mo               # Cryptographic key management\n    ├── PermissionHandler.mo        # Access control\n    ├── DIDDirectoryHandler.mo      # DID directory operations\n    └── ServerInfoHandler.mo        # Server metadata\n\nexamples/dao/\n├── src/backend/\n│   ├── main.mo                     # DAO canister\n│   ├── Orchestrator.mo             # Deployment orchestration\n│   ├── Logger.mo                   # Logging system\n│   ├── WasmStore.mo                # WASM module storage\n│   └── Proposals/                  # Proposal type implementations\n└── src/frontend/                   # Web UI for DAO management\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit issues and pull requests.\n\n## Dependencies\n\n- [motoko_atproto](https://github.com/edjCase/motoko_atproto) - AT Protocol libraries for Motoko\n- [Liminal](https://mops.one/liminal) - HTTP server framework\n- [certified-assets](https://mops.one/certified-assets) - Certified asset serving\n- [dao-proposal-engine](https://mops.one/dao-proposal-engine) - (Example code) DAO governance framework\n\n## License\n\nMIT License - see [LICENSE](LICENSE) file for details\n\n## Resources\n\n- [AT Protocol Documentation](https://atproto.com/)\n- [Internet Computer Documentation](https://internetcomputer.org/docs)\n- [Motoko Documentation](https://internetcomputer.org/docs/current/motoko/main/motoko)\n\n## Support\n\nFor questions and support:\n- Open an issue on GitHub\n- Check the [AT Protocol Discord](https://discord.gg/atproto)\n- Visit the [Internet Computer Forum](https://forum.dfinity.org/)\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FedjCase%2Fmotoko_atproto_pds","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FedjCase%2Fmotoko_atproto_pds","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FedjCase%2Fmotoko_atproto_pds/lists"}