Sign inAppearance settingsPlatformAI CODE CREATIONGitHub CopilotWrite better code with AIGitHub Copilot appDirect agents from issue to mergeMCP RegistryIntegrate external toolsDEVELOPER WORKFLOWSActionsAutomate any workflowCodespacesInstant dev environmentsIssuesPlan and track workCode ReviewManage code changesCode QualityEnforce quality at mergeAPPLICATION SECURITYGitHub Advanced SecurityFind and fix vulnerabilitiesCode securitySecure your code as you buildSecret protectionStop leaks before they startEXPLOREWhy GitHubDocumentationBlogChangelogMarketplaceView all featuresSolutionsBY COMPANY SIZEEnterprisesSmall and medium teamsStartupsNonprofitsBY USE CASEApp ModernizationDevSecOpsDevOpsCI/CDView all use casesBY INDUSTRYHealthcareFinancial servicesManufacturingGovernmentView all industriesView all solutionsResourcesEXPLORE BY TOPICAISoftware DevelopmentDevOpsSecurityView all topicsEXPLORE BY TYPECustomer storiesEvents & webinarsEbooks & reportsBusiness insightsGitHub SkillsSUPPORT & SERVICESDocumentationCustomer supportCommunity forumTrust centerPartnersView all resourcesOpen SourceCOMMUNITYGitHub SponsorsFund open source developersPROGRAMSSecurity LabMaintainer CommunityGitHub StarsArchive ProgramREPOSITORIESTopicsTrendingCollectionsEnterpriseENTERPRISE SOLUTIONSEnterprise platformAI-powered developer platformAVAILABLE ADD-ONSGitHub Advanced SecurityEnterprise-grade security featuresCopilot for BusinessEnterprise-grade AI featuresPremium SupportEnterprise-grade 24/7 supportPricingSearch/Sign inSign upAppearance settings You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session. Dismiss alert {{ message }} {"props":{"contextRegion":{"crumbs":[{"crumb_type":"organization","label":"Interpoll1","is_root":false,"href":"/Interpoll1"},{"crumb_type":"repository","label":"decentralised","is_root":false,"href":"/Interpoll1/decentralised"}],"localNavigation":[{"id":"code","icon":"code","label":"Code","href":"/Interpoll1/decentralised","selectedLinks":["repo_source","repo_downloads","repo_commits","repo_releases","repo_tags","repo_branches","repo_packages","repo_deployments","repo_attestations"],"popoverTarget":false,"commandId":"repositories:go-to-code","reactNav":{"appTarget":"code-view","anchor":"code-view-repo-link"},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"issues","icon":"issue-opened","label":"Issues","href":"/Interpoll1/decentralised/issues","selectedLinks":["repo_issues","repo_labels","repo_milestones"],"count":1,"popoverTarget":false,"commandId":"repositories:go-to-issues","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"pull-requests","icon":"git-pull-request","label":"Pull requests","href":"/Interpoll1/decentralised/pulls","selectedLinks":["repo_pulls","checks"],"count":2,"popoverTarget":false,"commandId":"repositories:go-to-pull-requests","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"actions","icon":"play","label":"Actions","href":"/Interpoll1/decentralised/actions","selectedLinks":["repo_actions"],"popoverTarget":false,"commandId":"repositories:go-to-actions","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"projects","icon":"table","label":"Projects","href":"/Interpoll1/decentralised/projects","selectedLinks":["repo_projects","new_repo_project","repo_project"],"popoverTarget":false,"commandId":"repositories:go-to-projects","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"security-and-quality","icon":"shield","label":"Security and quality","href":"/Interpoll1/decentralised/security","selectedLinks":["security","overview","alerts","policy","token_scanning","code_scanning"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-security","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"insights","icon":"graph","label":"Insights","href":"/Interpoll1/decentralised/pulse","selectedLinks":["repo_graphs","repo_contributors","dependency_graph","dependabot_updates","pulse","people","community"],"popoverTarget":false,"commandId":"repositories:go-to-insights","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}}],"localNavigationUpdateChannel":null,"selectedLink":"repo_source"},"owner":null,"headerLogo":{"href":"/","aria-label":"Homepage "},"notifications":{"indicatorMode":"disabled","websocketChannel":null,"fetchIndicatorSrc":"/notifications/indicator","fetchIndicatorEnabled":false},"issues":{"href":"/issues"},"pulls":{"href":"/pulls"},"contributedRepos":{"href":"/repos"},"copilot":{"show":false,"showAgentsButton":false,"showRelaunchAnnouncement":false,"copilotChatUrl":null,"copilotApiUrl":"https://api.githubcopilot.com"},"search":{"show":true,"showCommandPalette":false,"isSearchPage":false,"isJumpToSearch":false,"searchContext":{"scope":"repo:Interpoll1/decentralised","current_repo_name":"decentralised","current_repo_nwo":"Interpoll1/decentralised","current_repo_org":"Interpoll1","user_id":"Interpoll1"}},"commandPalette":null,"enterpriseBar":{"show":false},"globalTransactionalMessage":[],"payloadsUrl":"/_global-navigation/payloads.json?can_toggle_site_admin_and_employee_status=0\u0026is_admin_mode_on=0\u0026is_ui_opted_out=0\u0026show_ui_opt_out=0\u0026v=8","contextRegionUrl":"/_global-navigation/context-region.json"}} Interpoll1decentralisedRepository navigationCodeIssues1 (1)Pull requests2 (2)ActionsProjectsSecurity and qualityInsightsMore items {"payload":{"codeViewRepoRoute":{"path":"/","refInfo":{"name":"master","listCacheKey":"v0:1792083673.0","canEdit":false,"refType":"branch","currentOid":"29ded4f505ef55c4ad621b5e991a99879677fd4f"},"tree":{"items":[{"name":".github/workflows","path":".github/workflows","contentType":"directory","hasSimplifiedPath":true},{"name":"android","path":"android","contentType":"directory"},{"name":"community-relay","path":"community-relay","contentType":"directory"},{"name":"deployment/public-engagement-v1","path":"deployment/public-engagement-v1","contentType":"directory","hasSimplifiedPath":true},{"name":"design-system","path":"design-system","contentType":"directory"},{"name":"design","path":"design","contentType":"directory"},{"name":"different_favicons","path":"different_favicons","contentType":"directory"},{"name":"docs","path":"docs","contentType":"directory"},{"name":"e2e","path":"e2e","contentType":"directory"},{"name":"gun-relay","path":"gun-relay","contentType":"directory"},{"name":"moderation-api","path":"moderation-api","contentType":"directory"},{"name":"public","path":"public","contentType":"directory"},{"name":"relay-push","path":"relay-push","contentType":"directory"},{"name":"relay-server","path":"relay-server","contentType":"directory"},{"name":"resources","path":"resources","contentType":"directory"},{"name":"shared-validation","path":"shared-validation","contentType":"directory"},{"name":"src","path":"src","contentType":"directory"},{"name":"tests","path":"tests","contentType":"directory"},{"name":"tools","path":"tools","contentType":"directory"},{"name":"unit_tests","path":"unit_tests","contentType":"directory"},{"name":".env.example","path":".env.example","contentType":"file"},{"name":".gitattributes","path":".gitattributes","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":"LICENSE","path":"LICENSE","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"bash.sh","path":"bash.sh","contentType":"file"},{"name":"bot-detector.js","path":"bot-detector.js","contentType":"file"},{"name":"capacitor.config.ts","path":"capacitor.config.ts","contentType":"file"},{"name":"content-firewall.js","path":"content-firewall.js","contentType":"file"},{"name":"deploy-ftp.sh","path":"deploy-ftp.sh","contentType":"file"},{"name":"ecosystem.config.cjs","path":"ecosystem.config.cjs","contentType":"file"},{"name":"index.html","path":"index.html","contentType":"file"},{"name":"nginx-interpoll.conf","path":"nginx-interpoll.conf","contentType":"file"},{"name":"package-lock.json","path":"package-lock.json","contentType":"file"},{"name":"package.json","path":"package.json","contentType":"file"},{"name":"peer.js","path":"peer.js","contentType":"file"},{"name":"postcss.config.js","path":"postcss.config.js","contentType":"file"},{"name":"pow-challenge.js","path":"pow-challenge.js","contentType":"file"},{"name":"probe-community-writes.cjs","path":"probe-community-writes.cjs","contentType":"file"},{"name":"rate-limiter.js","path":"rate-limiter.js","contentType":"file"},{"name":"relay-integrity.js","path":"relay-integrity.js","contentType":"file"},{"name":"repair-community-husks.cjs","path":"repair-community-husks.cjs","contentType":"file"},{"name":"restore-gtaclips.cjs","path":"restore-gtaclips.cjs","contentType":"file"},{"name":"run.sh","path":"run.sh","contentType":"file"},{"name":"security-tests.js","path":"security-tests.js","contentType":"file"},{"name":"security-utils.js","path":"security-utils.js","contentType":"file"},{"name":"seed.js","path":"seed.js","contentType":"file"},{"name":"spam-scorer.js","path":"spam-scorer.js","contentType":"file"},{"name":"tailwind.config.js","path":"tailwind.config.js","contentType":"file"},{"name":"trustapi.ts","path":"trustapi.ts","contentType":"file"},{"name":"tsconfig.app.json","path":"tsconfig.app.json","contentType":"file"},{"name":"tsconfig.json","path":"tsconfig.json","contentType":"file"},{"name":"tsconfig.node.json","path":"tsconfig.node.json","contentType":"file"},{"name":"vercel.json","path":"vercel.json","contentType":"file"},{"name":"vite.config.ts","path":"vite.config.ts","contentType":"file"},{"name":"ws-validators.js","path":"ws-validators.js","contentType":"file"}],"totalCount":56,"templateDirectorySuggestionUrl":null,"readme":null,"showBranchInfobar":false},"userNameDisplayConfiguration":"handle","treeExpanded":false,"symbolsExpanded":false,"copilotSWEAgentEnabled":false,"copilotAccessAllowed":false,"isOverview":true,"overview":{"banners":{"shouldRecommendReadme":false,"isPersonalRepo":false,"showUseActionBanner":false,"actionSlug":null,"actionId":null,"showProtectBranchBanner":false,"requiredCustomPropertyValuesMissingCount":0,"transactionalMessageBanner":null,"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_repo","releasePath":"/Interpoll1/decentralised/releases/new?marketplace=true","showPublishActionBanner":false},"interactionLimitBanner":null,"showInvitationBanner":false,"inviterName":null,"actionsMigrationBannerInfo":{"releaseTags":[],"showImmutableActionsMigrationBanner":false,"initialMigrationStatus":null},"copilotSurveyBanner":null,"showSpammyBanner":false,"blockedContributorsBanner":null,"codespacesSurveyBanner":null},"codeButton":{"contactPath":"/contact","isEnterprise":false,"local":{"protocolInfo":{"httpAvailable":true,"sshAvailable":null,"httpUrl":"https://github.com/Interpoll1/decentralised.git","showCloneWarning":null,"sshUrl":null,"sshCertificatesRequired":null,"sshCertificatesAvailable":null,"ghCliUrl":"gh repo clone Interpoll1/decentralised","defaultProtocol":"http","newSshKeyUrl":"/settings/ssh/new","setProtocolPath":"/users/set_protocol?protocol_type=clone"},"platformInfo":{"cloneUrl":"https://desktop.github.com","showVisualStudioCloneButton":false,"visualStudioCloneUrl":"https://windows.github.com","showXcodeCloneButton":false,"xcodeCloneUrl":"xcode://clone?repo=https%3A%2F%2Fgithub.com%2FInterpoll1%2Fdecentralised","zipballUrl":"/Interpoll1/decentralised/archive/refs/heads/master.zip"}},"newCodespacePath":"/codespaces/new?hide_repo_select=true\u0026repo=1143479830"},"popovers":{"rename":null,"renamedParentRepo":null},"commitCount":"315","overviewFiles":[{"displayName":"README.md","repoName":"decentralised","refName":"master","path":"README.md","preferredFileType":"readme","tabName":"README","richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch1 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eInterPoll\u003c/h1\u003e\u003ca id=\"user-content-interpoll\" class=\"anchor\" aria-label=\"Permalink: InterPoll\" href=\"#interpoll\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cblockquote\u003e\n\u003cp dir=\"auto\"\u003ePolls, posts, and discussions that no single company can take down.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp align=\"center\" dir=\"auto\"\u003e\n \u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Interpoll1/decentralised/blob/master/docs/screenshots/homepage.jpg\"\u003e\u003cimg width=\"1920\" alt=\"InterPoll — decentralized polling platform\" src=\"/Interpoll1/decentralised/raw/master/docs/screenshots/homepage.jpg\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSo what is this, exactly?\u003c/h2\u003e\u003ca id=\"user-content-so-what-is-this-exactly\" class=\"anchor\" aria-label=\"Permalink: So what is this, exactly?\" href=\"#so-what-is-this-exactly\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eInterPoll is a free, open-source polling and discussion platform where \u003cstrong\u003eyour data doesn't live on someone else's server\u003c/strong\u003e. When you vote, post, or comment, everything is stored on your device first, then replicated across the network. No central database. No single point of failure. No one person who can flip a switch and erase your community's history.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eWe built it because we kept running into the same problem: platforms censor, shadow-ban, or just quietly remove things. On InterPoll, you create your own community, set your own rules, and no algorithm decides what shows up in your feed. If a relay goes down, the data lives on in every peer that has a copy.\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eWhat makes it different\u003c/h2\u003e\u003ca id=\"user-content-what-makes-it-different\" class=\"anchor\" aria-label=\"Permalink: What makes it different\" href=\"#what-makes-it-different\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eYour vote is cryptographically signed.\u003c/strong\u003e A key lives on your device and signs every action you take. No relay or server can forge a vote in your name.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEvery vote chains to the last one.\u003c/strong\u003e Tamper with a past record and the chain snaps — visibly. You can verify this yourself in the built-in Chain Explorer.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePosts and comments replicate everywhere.\u003c/strong\u003e Not locked in a vendor database. They spread across peers and relay servers, and survive as long as anyone holds a copy.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWorks offline.\u003c/strong\u003e Vote without internet. Your data syncs when connectivity comes back.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEncrypted communities.\u003c/strong\u003e Create spaces where everything is encrypted in your browser. Relay servers see scrambled data — they can't read it even if they wanted to.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eNo algorithm, no shadow-banning.\u003c/strong\u003e You see what your community posts. That's it.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAnyone can run a relay.\u003c/strong\u003e Communities can host their own, giving them full sovereignty over their data.\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eHow it actually works\u003c/h2\u003e\u003ca id=\"user-content-how-it-actually-works\" class=\"anchor\" aria-label=\"Permalink: How it actually works\" href=\"#how-it-actually-works\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eInterPoll runs on three layers that work together:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eYour local chain\u003c/strong\u003e — Every vote and key action gets written to a tamper-evident log right in your browser. Each entry links to the one before it with a cryptographic hash. Change anything and the chain breaks.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eThe distributed network\u003c/strong\u003e — Polls, posts, comments, communities, and profiles replicate across a peer-to-peer database (GunDB). Every connected device holds a copy. A relay going down doesn't kill the data — it syncs back up when any peer reconnects.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eThe relay\u003c/strong\u003e — A lightweight WebSocket server helps devices find each other and share updates in real time. Anyone can run one. If one gets blocked, peers switch to another. More relays = more resilience.\u003c/p\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"a2ecc149-43dd-43b2-b5aa-af6618dbaff8\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;graph TD\\n A[Your Browser] --\u0026gt;|signs \u0026amp; stores actions| B[Local Chain — your device]\\n A --\u0026gt;|replicates polls, posts, comments| C[GunDB — distributed]\\n A --\u0026gt;|syncs new blocks in real time| D[WebSocket Relay]\\n A --\u0026gt;|syncs across your own tabs| E[BroadcastChannel]\\n D --\u0026gt;|broadcasts to| F[Other Participants]\\n C --\u0026gt;|replicates to| G[Community Relay Servers]\\n\u0026quot;}\" data-plain=\"graph TD\n A[Your Browser] --\u0026gt;|signs \u0026amp; stores actions| B[Local Chain — your device]\n A --\u0026gt;|replicates polls, posts, comments| C[GunDB — distributed]\n A --\u0026gt;|syncs new blocks in real time| D[WebSocket Relay]\n A --\u0026gt;|syncs across your own tabs| E[BroadcastChannel]\n D --\u0026gt;|broadcasts to| F[Other Participants]\n C --\u0026gt;|replicates to| G[Community Relay Servers]\n\" dir=\"auto\"\u003e\n \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003egraph TD\n A[Your Browser] --\u0026gt;|signs \u0026amp; stores actions| B[Local Chain — your device]\n A --\u0026gt;|replicates polls, posts, comments| C[GunDB — distributed]\n A --\u0026gt;|syncs new blocks in real time| D[WebSocket Relay]\n A --\u0026gt;|syncs across your own tabs| E[BroadcastChannel]\n D --\u0026gt;|broadcasts to| F[Other Participants]\n C --\u0026gt;|replicates to| G[Community Relay Servers]\n\u003c/pre\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n \u003cspan data-view-component=\"true\"\u003e\n \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n \u003c/span\u003e\n\u003c/section\u003e\n\n\u003cblockquote\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eThe short version:\u003c/strong\u003e your polls, posts, and vote history exist on your device, your peers' devices, and across relay servers — all at once. Erasing them would mean erasing every copy simultaneously. Sooner or later, a peer with a copy reconnects and reseeds the network.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChain Explorer\u003c/h2\u003e\u003ca id=\"user-content-chain-explorer\" class=\"anchor\" aria-label=\"Permalink: Chain Explorer\" href=\"#chain-explorer\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eEvery vote is part of a verifiable chain. After voting, you get a short verification code — enter it in the Chain Explorer to confirm your vote is intact and hasn't been altered.\u003c/p\u003e\n\u003cp align=\"center\" dir=\"auto\"\u003e\n \u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Interpoll1/decentralised/blob/master/docs/screenshots/chain-explorer.jpg\"\u003e\u003cimg width=\"1920\" alt=\"Chain Explorer — blockchain verification with Schnorr signatures\" src=\"/Interpoll1/decentralised/raw/master/docs/screenshots/chain-explorer.jpg\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eResilience Center\u003c/h2\u003e\u003ca id=\"user-content-resilience-center\" class=\"anchor\" aria-label=\"Permalink: Resilience Center\" href=\"#resilience-center\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eMonitor your network health, scan for relays, switch to backups instantly, and fall back to a deterministic rendezvous point if every relay gets blocked.\u003c/p\u003e\n\u003cp align=\"center\" dir=\"auto\"\u003e\n \u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Interpoll1/decentralised/blob/master/docs/screenshots/resilience-center.jpg\"\u003e\u003cimg width=\"1920\" alt=\"Resilience Center — network health and relay management\" src=\"/Interpoll1/decentralised/raw/master/docs/screenshots/resilience-center.jpg\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eCryptographic Identity\u003c/h2\u003e\u003ca id=\"user-content-cryptographic-identity\" class=\"anchor\" aria-label=\"Permalink: Cryptographic Identity\" href=\"#cryptographic-identity\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eYour signing keys are generated and stored locally. Schnorr signatures (secp256k1) prove that every action came from your device — no one can impersonate you.\u003c/p\u003e\n\u003cp align=\"center\" dir=\"auto\"\u003e\n \u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Interpoll1/decentralised/blob/master/docs/screenshots/settings.jpg\"\u003e\u003cimg width=\"1920\" alt=\"Settings — cryptographic identity with Schnorr keypair\" src=\"/Interpoll1/decentralised/raw/master/docs/screenshots/settings.jpg\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFeatures at a glance\u003c/h2\u003e\u003ca id=\"user-content-features-at-a-glance\" class=\"anchor\" aria-label=\"Permalink: Features at a glance\" href=\"#features-at-a-glance\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eFeature\u003c/th\u003e\n\u003cth\u003eWhat it means\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eTamper-evident voting\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eEvery vote chains to the previous one. Alter a record and the chain breaks visibly.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eVerifiable receipts\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eGet a code after voting. Check it anytime in the Chain Explorer.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003ePosts \u0026amp; threaded comments\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eFull community discussions — publish updates, debate in comments, keep context attached to each poll.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eOffline-first\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eVote without internet. Records sync when you reconnect.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eEncrypted communities\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eAll content encrypted in-browser (AES-256-GCM). Relays see only ciphertext.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eCommunity-run relays\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eAny group can host their own relay server for full data sovereignty.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eFallback rendezvous\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eWhen all relays are blocked, nodes derive a shared reconnection point and self-heal.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eOptional login gates\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003ePoll creators can require Google or Microsoft sign-in for verified polls.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eInvite-only polls\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eSingle-use invite codes — each one works exactly once.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003ePeer-to-peer calls\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e1:1 voice/video from a DM. Media goes directly between browsers, DTLS-SRTP encrypted. If NAT blocks a direct path, the relay's TURN server forwards the \u003cem\u003eencrypted\u003c/em\u003e media. It sees both IPs and traffic volume, never content. Off in Anonymity Mode.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eCensorship-resistant by design\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eThe relay can delay messages, but it cannot forge a signed action from your device key.\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eHonest about the limits\u003c/h2\u003e\u003ca id=\"user-content-honest-about-the-limits\" class=\"anchor\" aria-label=\"Permalink: Honest about the limits\" href=\"#honest-about-the-limits\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThis is designed to be \u003cstrong\u003eharder to censor and tamper with\u003c/strong\u003e than a traditional platform — not impossible. Here's what that means in practice:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eData survives as long as \u003cstrong\u003eat least one honest participant\u003c/strong\u003e holds a copy and eventually reconnects.\u003c/li\u003e\n\u003cli\u003eA relay can \u003cstrong\u003edelay or censor\u003c/strong\u003e messages, but it \u003cstrong\u003ecannot forge\u003c/strong\u003e a vote or signed action from your device key.\u003c/li\u003e\n\u003cli\u003eAnti-fraud measures (device fingerprinting, two-phase vote authorization, invite codes, OAuth gating) \u003cstrong\u003eraise the cost\u003c/strong\u003e of duplicate voting. They don't provide mathematical one-person-one-vote guarantees.\u003c/li\u003e\n\u003cli\u003ePrivate community encryption is strong (AES-256-GCM), but if you lose your key, there is no recovery. That's by design.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp dir=\"auto\"\u003eFor the full technical threat model, see the \u003ca href=\"/Interpoll1/decentralised/blob/master/docs/protocol/IPP-00-overview.md\"\u003eIPP specification series\u003c/a\u003e.\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eGet involved\u003c/h2\u003e\u003ca id=\"user-content-get-involved\" class=\"anchor\" aria-label=\"Permalink: Get involved\" href=\"#get-involved\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eRun a peer\u003c/strong\u003e — the simplest way to strengthen the network. Another device running the app means another copy of the data and faster sync for everyone.\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"node peer.js\"\u003e\u003cpre\u003enode peer.js\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eRun a relay\u003c/strong\u003e — give your community full data sovereignty. See \u003ccode\u003egun-relay-server/\u003c/code\u003e for the GunDB relay and \u003ccode\u003erelay-server.js\u003c/code\u003e for the WebSocket relay.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eContribute\u003c/strong\u003e — the project is fully open source. Open a PR.\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eQuick start\u003c/h2\u003e\u003ca id=\"user-content-quick-start\" class=\"anchor\" aria-label=\"Permalink: Quick start\" href=\"#quick-start\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"# Start everything in tmux (recommended)\nchmod +x run.sh\n./run.sh\n\n# Or run each service manually:\nnpm run dev # Vite frontend → http://localhost:5173\nnode relay-server.js # WebSocket relay → ws://localhost:8080\ncd gun-relay-server \u0026amp;\u0026amp; node gun-relay.js # GunDB relay → http://localhost:8765/gun\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Start everything in tmux (recommended)\u003c/span\u003e\nchmod +x run.sh\n./run.sh\n\n\u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Or run each service manually:\u003c/span\u003e\nnpm run dev \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Vite frontend → http://localhost:5173\u003c/span\u003e\nnode relay-server.js \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e WebSocket relay → ws://localhost:8080\u003c/span\u003e\n\u003cspan class=\"pl-c1\"\u003ecd\u003c/span\u003e gun-relay-server \u003cspan class=\"pl-k\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e node gun-relay.js \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e GunDB relay → http://localhost:8765/gun\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eEnvironment variables\u003c/h3\u003e\u003ca id=\"user-content-environment-variables\" class=\"anchor\" aria-label=\"Permalink: Environment variables\" href=\"#environment-variables\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eFrontend\u003c/strong\u003e (set at build time with \u003ccode\u003eVITE_\u003c/code\u003e prefix):\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eVariable\u003c/th\u003e\n\u003cth\u003eDefault\u003c/th\u003e\n\u003cth\u003ePurpose\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eVITE_WS_RELAY_URL\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003ews://localhost:8080\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eWebSocket relay\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eVITE_GUN_RELAY_URL\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003ehttp://localhost:8765/gun\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eGunDB relay\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eVITE_API_BASE_URL\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003ehttp://localhost:8080\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eBackend API\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eRelay URLs can also be changed at runtime from Settings (saved in \u003ccode\u003elocalStorage\u003c/code\u003e).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003eRelay server:\u003c/strong\u003e\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eVariable\u003c/th\u003e\n\u003cth\u003eDefault\u003c/th\u003e\n\u003cth\u003ePurpose\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eFRONTEND_ORIGIN\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003ehttp://localhost:5173\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eCORS origin\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eSERVER_ORIGIN\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003ehttp://localhost:8080\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003ePublic relay origin for OAuth callbacks (must be HTTPS in production)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eJWT_SECRET\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003erandom per process\u003c/td\u003e\n\u003ctd\u003eHMAC secret for session JWTs\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eVOTE_RESERVATION_SECRET\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003erandom per process\u003c/td\u003e\n\u003ctd\u003eHMAC secret for vote reservation tokens\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eGOOGLE_CLIENT_ID\u003c/code\u003e / \u003ccode\u003eGOOGLE_CLIENT_SECRET\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e—\u003c/td\u003e\n\u003ctd\u003eGoogle OAuth (optional)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eMS_CLIENT_ID\u003c/code\u003e / \u003ccode\u003eMS_CLIENT_SECRET\u003c/code\u003e / \u003ccode\u003eMS_TENANT\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e—\u003c/td\u003e\n\u003ctd\u003eMicrosoft OAuth (optional)\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eOAuth is optional — only needed for polls that require a login to vote. Use \u003ccode\u003e.env.example\u003c/code\u003e as a template.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eBuild commands\u003c/h3\u003e\u003ca id=\"user-content-build-commands\" class=\"anchor\" aria-label=\"Permalink: Build commands\" href=\"#build-commands\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"npm run dev # Dev server\nnpm run build # Type-check + production build\nnpm run preview # Serve built dist/\nnpm test # Vitest test suite\"\u003e\u003cpre\u003enpm run dev \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Dev server\u003c/span\u003e\nnpm run build \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Type-check + production build\u003c/span\u003e\nnpm run preview \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Serve built dist/\u003c/span\u003e\nnpm \u003cspan class=\"pl-c1\"\u003etest\u003c/span\u003e \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Vitest test suite\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eArchitecture\u003c/h2\u003e\u003ca id=\"user-content-architecture\" class=\"anchor\" aria-label=\"Permalink: Architecture\" href=\"#architecture\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"src/\n components/ UI components (VoteForm, PollCard, PostCard, etc.)\n views/ Page-level views (HomePage, PollDetailPage, SettingsPage, etc.)\n services/ Core logic — blockchain, GunDB, WebSocket, crypto, storage\n stores/ Pinia state stores (chainStore, pollStore, communityStore, etc.)\n router/ Vue Router configuration\n composables/ Reusable Vue 3 composition functions\n config.ts Runtime-mutable relay URLs and app configuration\n\nrelay-server.js Dev WebSocket relay + OAuth + vote authorization\nrelay-server/relay-server-enhanced.js Production relay (PM2, persisted vote registry)\ngun-relay-server/ GunDB relay server\nshared-validation/ Validation shared between frontend and relay\"\u003e\u003cpre class=\"notranslate\"\u003e\u003ccode\u003esrc/\n components/ UI components (VoteForm, PollCard, PostCard, etc.)\n views/ Page-level views (HomePage, PollDetailPage, SettingsPage, etc.)\n services/ Core logic — blockchain, GunDB, WebSocket, crypto, storage\n stores/ Pinia state stores (chainStore, pollStore, communityStore, etc.)\n router/ Vue Router configuration\n composables/ Reusable Vue 3 composition functions\n config.ts Runtime-mutable relay URLs and app configuration\n\nrelay-server.js Dev WebSocket relay + OAuth + vote authorization\nrelay-server/relay-server-enhanced.js Production relay (PM2, persisted vote registry)\ngun-relay-server/ GunDB relay server\nshared-validation/ Validation shared between frontend and relay\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eKey services\u003c/h3\u003e\u003ca id=\"user-content-key-services\" class=\"anchor\" aria-label=\"Permalink: Key services\" href=\"#key-services\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eService\u003c/th\u003e\n\u003cth\u003eWhat it does\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003echainService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eBlock creation, hashing, signing, chain validation\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003egunService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eGunDB read/write/subscribe wrapper\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ewebsocketService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eWebSocket connection, peer discovery, relay failover\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003epollService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003ePoll CRUD, invite codes\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eencryptionService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eAES-256-GCM encryption for private communities\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ekeyVaultService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eLocal key storage and export/import\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ecryptoService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eSHA-256 hashing, verification codes\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003evoteTrackerService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eDevice fingerprinting, duplicate-vote prevention\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003estorageService\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eIndexedDB wrapper for blocks, votes, receipts\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eVote flow\u003c/h3\u003e\u003ca id=\"user-content-vote-flow\" class=\"anchor\" aria-label=\"Permalink: Vote flow\" href=\"#vote-flow\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"7740782f-c736-40da-ac60-6ac5a04dee86\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;graph LR\\n G[Genesis Block] --\u0026gt;|hash link| B1[Block 1 — Vote A]\\n B1 --\u0026gt;|hash link| B2[Block 2 — Vote B]\\n B2 --\u0026gt;|hash link| B3[Block 3 — Vote C]\\n\u0026quot;}\" data-plain=\"graph LR\n G[Genesis Block] --\u0026gt;|hash link| B1[Block 1 — Vote A]\n B1 --\u0026gt;|hash link| B2[Block 2 — Vote B]\n B2 --\u0026gt;|hash link| B3[Block 3 — Vote C]\n\" dir=\"auto\"\u003e\n \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003egraph LR\n G[Genesis Block] --\u0026gt;|hash link| B1[Block 1 — Vote A]\n B1 --\u0026gt;|hash link| B2[Block 2 — Vote B]\n B2 --\u0026gt;|hash link| B3[Block 3 — Vote C]\n\u003c/pre\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n \u003cspan data-view-component=\"true\"\u003e\n \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n \u003c/span\u003e\n\u003c/section\u003e\n\n\u003col dir=\"auto\"\u003e\n\u003cli\u003eVote payload is hashed (SHA-256) and a new block is appended, linked to the previous block's hash.\u003c/li\u003e\n\u003cli\u003eThe block is signed with your device key and saved locally.\u003c/li\u003e\n\u003cli\u003eA receipt with a verification code is generated.\u003c/li\u003e\n\u003cli\u003eThe block is broadcast to peers via WebSocket and BroadcastChannel.\u003c/li\u003e\n\u003cli\u003eThe relay's two-phase path (\u003ccode\u003e/api/vote-authorize\u003c/code\u003e → \u003ccode\u003e/api/vote-confirm\u003c/code\u003e) prevents duplicate registration server-side.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAnti-fraud layers\u003c/h3\u003e\u003ca id=\"user-content-anti-fraud-layers\" class=\"anchor\" aria-label=\"Permalink: Anti-fraud layers\" href=\"#anti-fraud-layers\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eDevice fingerprinting\u003c/strong\u003e — SHA-256 hash of browser properties → persistent device ID\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTwo-phase authorization\u003c/strong\u003e — relay issues a short-lived reservation token; only confirming with it commits the vote\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSingle-use invite codes\u003c/strong\u003e — consumed atomically in GunDB\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eOptional OAuth gating\u003c/strong\u003e — Google or Microsoft login required to vote\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRate limiting and bot scoring\u003c/strong\u003e — reduces automated spam\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eProof-of-Work (optional)\u003c/strong\u003e — raises the cost of high-frequency message floods\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eProtocol spec\u003c/h2\u003e\u003ca id=\"user-content-protocol-spec\" class=\"anchor\" aria-label=\"Permalink: Protocol spec\" href=\"#protocol-spec\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eFor the full technical specification — block structure, vote flow, sync protocol, encryption, relay trust model, and threat model — see the numbered IPP series:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e\u003ca href=\"/Interpoll1/decentralised/blob/master/docs/protocol/IPP-00-overview.md\"\u003e\u003ccode\u003edocs/protocol/IPP-00-overview.md\u003c/code\u003e\u003c/a\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eLicense\u003c/h2\u003e\u003ca id=\"user-content-license\" class=\"anchor\" aria-label=\"Permalink: License\" href=\"#license\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eOpen source. See \u003ca href=\"/Interpoll1/decentralised/blob/master/LICENSE\"\u003eLICENSE\u003c/a\u003e for details.\u003c/p\u003e\n\u003c/article\u003e","loaded":true,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":[{"level":1,"text":"InterPoll","anchor":"interpoll","htmlText":"InterPoll"},{"level":2,"text":"So what is this, exactly?","anchor":"so-what-is-this-exactly","htmlText":"So what is this, exactly?"},{"level":2,"text":"What makes it different","anchor":"what-makes-it-different","htmlText":"What makes it different"},{"level":2,"text":"How it actually works","anchor":"how-it-actually-works","htmlText":"How it actually works"},{"level":2,"text":"Chain Explorer","anchor":"chain-explorer","htmlText":"Chain Explorer"},{"level":2,"text":"Resilience Center","anchor":"resilience-center","htmlText":"Resilience Center"},{"level":2,"text":"Cryptographic Identity","anchor":"cryptographic-identity","htmlText":"Cryptographic Identity"},{"level":2,"text":"Features at a glance","anchor":"features-at-a-glance","htmlText":"Features at a glance"},{"level":2,"text":"Honest about the limits","anchor":"honest-about-the-limits","htmlText":"Honest about the limits"},{"level":2,"text":"Get involved","anchor":"get-involved","htmlText":"Get involved"},{"level":2,"text":"Quick start","anchor":"quick-start","htmlText":"Quick start"},{"level":3,"text":"Environment variables","anchor":"environment-variables","htmlText":"Environment variables"},{"level":3,"text":"Build commands","anchor":"build-commands","htmlText":"Build commands"},{"level":2,"text":"Architecture","anchor":"architecture","htmlText":"Architecture"},{"level":3,"text":"Key services","anchor":"key-services","htmlText":"Key services"},{"level":3,"text":"Vote flow","anchor":"vote-flow","htmlText":"Vote flow"},{"level":3,"text":"Anti-fraud layers","anchor":"anti-fraud-layers","htmlText":"Anti-fraud layers"},{"level":2,"text":"Protocol spec","anchor":"protocol-spec","htmlText":"Protocol spec"},{"level":2,"text":"License","anchor":"license","htmlText":"License"}],"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2FInterpoll1%2Fdecentralised"}},{"displayName":"LICENSE","repoName":"decentralised","refName":"master","path":"LICENSE","preferredFileType":"license","tabName":"Unlicense","richText":null,"loaded":false,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":null,"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2FInterpoll1%2Fdecentralised"}}],"overviewFilesProcessingTime":0,"copilotSWEAgentEnabled":false,"sidebarAboutData":{"topics":[],"showInsights":true,"canOpenStargazersAndWatchers":false,"stargazerCount":15,"watcherCount":1,"forksCount":4,"stargazersPath":"/Interpoll1/decentralised/stargazers","watchersPath":"/Interpoll1/decentralised/watchers","forkNetworkPath":"/Interpoll1/decentralised/forks","activityPath":"/Interpoll1/decentralised/activity","customPropertiesPath":"/Interpoll1/decentralised/custom-properties","reportPath":"/contact/report-content?content_url=https%3A%2F%2Fgithub.com%2FInterpoll1%2Fdecentralised\u0026report=Interpoll1+%28user%29","canEditMetadata":false,"ownerLogin":"Interpoll1","repoName":"decentralised","isOrg":true,"sections":{"releases":{"releaseCount":8,"tagCount":8},"sponsors":false,"deployments":false,"packages":true,"usedBy":false,"contributors":true,"languages":true,"cta":false,"suggestedWorkflows":false},"hasCitation":false,"repo":{"ownerAvatarUrl":"https://avatars.githubusercontent.com/u/293719886?s=60\u0026v=4","ownerId":293719886,"isPrivate":false,"isTemplate":false,"isFork":false,"isMirror":false,"isArchived":false,"visibilityLabel":"Public","license":{"spdxId":"Unlicense","name":"The Unlicense"},"isAdvisoryWorkspace":false},"star":{"viewerHasStarred":false,"canStar":false},"fork":{"canFork":false,"forkabilityError":"not_logged_in"},"pin":{"canPin":false,"isPinned":false,"isOrgOwned":true,"pinItemsRemaining":6},"watch":{"canWatch":false,"watchData":{"repositoryId":"1143479830","repositoryName":"Interpoll1/decentralised","watchersCount":1,"subscriptionType":"none","subscribableThreadTypes":[{"name":"Issue","enabled":true,"subscribed":false},{"name":"PullRequest","enabled":true,"subscribed":false},{"name":"Release","enabled":true,"subscribed":false},{"name":"Discussion","enabled":false,"subscribed":false},{"name":"SecurityAlert","enabled":true,"subscribed":false}],"showLabelSubscriptions":false,"subscribedLabels":[]}},"viewer":{"isLoggedIn":false,"isSiteAdmin":false,"emuContributionBlocked":false},"showSponsorButton":false,"showTemplateButton":false,"showDemoNotification":false}}},"codeViewLayoutRoute":{"repo":{"id":1143479830,"defaultBranch":"master","name":"decentralised","ownerLogin":"Interpoll1","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2026-01-27T16:24:19.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/293719886?v=4","public":true,"private":false,"isOrgOwned":true,"isArchived":false},"currentUser":null,"uploadToken":"1uHg9NNZJGMR_fFdnxJDfrz8r6Xbxv0radRXtnd6N7RX5NUixPHvjsq8cH41tijw6earebUwB6knpAhm2bSJcw","allShortcutsEnabled":false,"treeExpanded":true,"path":"/","symbolsExpanded":false,"refInfo":{"name":"master","listCacheKey":"v0:1792083673.0","canEdit":false,"currentOid":"29ded4f505ef55c4ad621b5e991a99879677fd4f"},"helpUrl":"https://docs.github.com","githubDevUrl":null},"sidebarAbout":{"topics":[],"showInsights":true,"canOpenStargazersAndWatchers":false,"stargazerCount":15,"watcherCount":1,"forksCount":4,"stargazersPath":"/Interpoll1/decentralised/stargazers","watchersPath":"/Interpoll1/decentralised/watchers","forkNetworkPath":"/Interpoll1/decentralised/forks","activityPath":"/Interpoll1/decentralised/activity","customPropertiesPath":"/Interpoll1/decentralised/custom-properties","reportPath":"/contact/report-content?content_url=https%3A%2F%2Fgithub.com%2FInterpoll1%2Fdecentralised\u0026report=Interpoll1+%28user%29","canEditMetadata":false,"ownerLogin":"Interpoll1","repoName":"decentralised","isOrg":true,"sections":{"releases":{"releaseCount":8,"tagCount":8},"sponsors":false,"deployments":false,"packages":true,"usedBy":false,"contributors":true,"languages":true,"cta":false,"suggestedWorkflows":false},"hasCitation":false,"repo":{"ownerAvatarUrl":"https://avatars.githubusercontent.com/u/293719886?s=60\u0026v=4","ownerId":293719886,"isPrivate":false,"isTemplate":false,"isFork":false,"isMirror":false,"isArchived":false,"visibilityLabel":"Public","license":{"spdxId":"Unlicense","name":"The Unlicense"},"isAdvisoryWorkspace":false},"star":{"viewerHasStarred":false,"canStar":false},"fork":{"canFork":false,"forkabilityError":"not_logged_in"},"pin":{"canPin":false,"isPinned":false,"isOrgOwned":true,"pinItemsRemaining":6},"watch":{"canWatch":false,"watchData":{"repositoryId":"1143479830","repositoryName":"Interpoll1/decentralised","watchersCount":1,"subscriptionType":"none","subscribableThreadTypes":[{"name":"Issue","enabled":true,"subscribed":false},{"name":"PullRequest","enabled":true,"subscribed":false},{"name":"Release","enabled":true,"subscribed":false},{"name":"Discussion","enabled":false,"subscribed":false},{"name":"SecurityAlert","enabled":true,"subscribed":false}],"showLabelSubscriptions":false,"subscribedLabels":[]}},"viewer":{"isLoggedIn":false,"isSiteAdmin":false,"emuContributionBlocked":false},"showSponsorButton":false,"showTemplateButton":false,"showDemoNotification":false},"csrf_tokens":{"/Interpoll1/decentralised/branches":{"post":"U05nBQ0m65q6APk0HqUSD8ZBK4mCk0chLDx4_RQ0HyQqYKVzVxOEcxFa0hIw6HAoAn38HEr5gqnLOeD5p68ukA"}}},"title":"GitHub - Interpoll1/decentralised","appPayload":{},"meta":{"title":"GitHub - Interpoll1/decentralised"}} decentralisedPublicNotificationsYou must be signed in to change notification settingsFork4 (4)Star15 (15)You must be signed in to star a repositoryAboutNo description, website, or topics provided.ResourcesReadmeUnlicenseActivityCustom propertiesStars15 starsWatchers1 watchingForks4 forksReport repositorymasterBranchesTagsGo to fileCodeOpen more actions menuLatest commit History315 Commits315 CommitsFolders and filesNameNameLast commit messageLast commit date.github/workflows.github/workflows androidandroid community-relaycommunity-relay deployment/public-engagement-v1deployment/public-engagement-v1 design-systemdesign-system designdesign different_faviconsdifferent_favicons docsdocs e2ee2e gun-relaygun-relay moderation-apimoderation-api publicpublic relay-pushrelay-push relay-serverrelay-server resourcesresources shared-validationshared-validation srcsrc teststests toolstools unit_testsunit_tests .env.example.env.example .gitattributes.gitattributes .gitignore.gitignore LICENSELICENSE README.mdREADME.md bash.shbash.sh bot-detector.jsbot-detector.js capacitor.config.tscapacitor.config.ts content-firewall.jscontent-firewall.js deploy-ftp.shdeploy-ftp.sh ecosystem.config.cjsecosystem.config.cjs index.htmlindex.html nginx-interpoll.confnginx-interpoll.conf package-lock.jsonpackage-lock.json package.jsonpackage.json peer.jspeer.js postcss.config.jspostcss.config.js pow-challenge.jspow-challenge.js probe-community-writes.cjsprobe-community-writes.cjs rate-limiter.jsrate-limiter.js relay-integrity.jsrelay-integrity.js repair-community-husks.cjsrepair-community-husks.cjs restore-gtaclips.cjsrestore-gtaclips.cjs run.shrun.sh security-tests.jssecurity-tests.js security-utils.jssecurity-utils.js seed.jsseed.js spam-scorer.jsspam-scorer.js tailwind.config.jstailwind.config.js trustapi.tstrustapi.ts tsconfig.app.jsontsconfig.app.json tsconfig.jsontsconfig.json tsconfig.node.jsontsconfig.node.json vercel.jsonvercel.json vite.config.tsvite.config.ts ws-validators.jsws-validators.js View all filesRepository files navigationREADMEUnlicense licenseMore itemsInterPoll Polls, posts, and discussions that no single company can take down.
InterPoll is a free, open-source polling and discussion platform where your data doesn't live on someone else's server. When you vote, post, or comment, everything is stored on your device first, then replicated across the network. No central database. No single point of failure. No one person who can flip a switch and erase your community's history.
We built it because we kept running into the same problem: platforms censor, shadow-ban, or just quietly remove things. On InterPoll, you create your own community, set your own rules, and no algorithm decides what shows up in your feed. If a relay goes down, the data lives on in every peer that has a copy.
Your vote is cryptographically signed. A key lives on your device and signs every action you take. No relay or server can forge a vote in your name. Every vote chains to the last one. Tamper with a past record and the chain snaps — visibly. You can verify this yourself in the built-in Chain Explorer. Posts and comments replicate everywhere. Not locked in a vendor database. They spread across peers and relay servers, and survive as long as anyone holds a copy. Works offline. Vote without internet. Your data syncs when connectivity comes back. Encrypted communities. Create spaces where everything is encrypted in your browser. Relay servers see scrambled data — they can't read it even if they wanted to. No algorithm, no shadow-banning. You see what your community posts. That's it. Anyone can run a relay. Communities can host their own, giving them full sovereignty over their data. How it actually works InterPoll runs on three layers that work together:
Your local chain — Every vote and key action gets written to a tamper-evident log right in your browser. Each entry links to the one before it with a cryptographic hash. Change anything and the chain breaks.
This article was aggregated automatically by CyberWire Daily's newsfeed engine. Original reporting: github.com.