Integration guide

Add BitGate to your site

No bundler required. Build the widget once, self-host it, and configure everything in markup. Then replace the preset with thresholds that fit your own community.

  1. Build the widget from source

    Clone the repository and build. The output is a single self-contained file with no runtime dependencies.

    git clone https://github.com/PR0M3TH3AN/Nostr-Governance
    cd Nostr-Governance
    npm install
    npm run build:widget      # → packages/widget/dist/bitgate.js
    The bundle is deliberately not committed. A moderation widget decides what people can see; build it from source you have read rather than trusting a prebuilt minified blob. Copy the result onto your own origin — do not hot-link this site's copy.
  2. Include the script and the provider

    <bitgate-provider> builds the runtime, loads administrative state from your relays, and shares both with every element inside it.

    <script type="module" src="/vendor/bitgate/bitgate.js"></script>
    
    <bitgate-provider
      relays="wss://relay-one.example,wss://relay-two.example"
      root="<your-root-pubkey>"
      policy="social">
      <!-- everything governed goes in here -->
    </bitgate-provider>

    root is your deployment's administrator pubkey — the key that publishes the moderator roster. It is configuration, not something discovered at runtime.

  3. Wrap what should be governed

    Elements read their target from attributes, so a server-rendered page needs no JavaScript at all.

    <bitgate-veil profile="feed" target-user="<author-pubkey>">
      <article>…your post markup…</article>
    </bitgate-veil>
    
    <bitgate-veil profile="feed" target-event="<event-id>" target-author="<pubkey>">…</bitgate-veil>
    
    <bitgate-veil profile="browse" target-address="30078:<pubkey>:sku-001">…</bitgate-veil>

    The veil applies the decision: blur on restrict, a disclosure with a "show anyway" control on hide, a warning line on warn. Assigning .target in JavaScript still works and wins over the attribute.

  4. Connect a signer

    BitGate needs to know who is looking, both to build the trust graph and to check what they may do. BitLogin's provider fits without an adapter.

    const provider = document.querySelector("bitgate-provider");
    await provider.ready;
    await provider.useSigner(window.nostr);
    
    // BitGate reads the follow graph from your app — it has no opinion
    // about how you fetch kind:3.
    provider.runtime.trust.setContacts(followList);
    Await ready, don't listen for it. The bitgate:ready event fires during element upgrade, which for a page that defines elements after parsing is before a listener could be attached. The promise never has that race.
  5. Replace the preset

    Presets exist so the first run works, not because the engine has an opinion. Every number in them is a starting point.

    import { createPolicyDefinition } from "@bitgate/core";
    
    const policy = createPolicyDefinition({
      id: "my-app",
      version: "1.0.0",
      defaultProfile: "feed",
      profiles: {
        feed: {
          name: "feed",
          administrativeDeny: { visibility: "hide", interaction: "deny" },
          reports: {
            malware: { hide: 1, interactionDeny: 1 },
            spam:    { downrank: 2, restrict: 4, hide: 8 },
            default: { downrank: 2, warn: 4 },
          },
          mutes: { default: { downrank: 1, hide: 12 } },
          muteWindowSeconds: 60 * 24 * 60 * 60,
        },
      },
    });
    
    provider.runtime.policies.setLocalPolicy(policy);

Elements

Embed only what you need; each is independent.

ElementWhat it does
bitgate-providerBuilds the runtime from attributes and shares it
bitgate-veilApplies a decision to whatever it wraps
bitgate-reportReport dialog — needs no capability, anyone may report
bitgate-statusEffect, reasons, and evidence for one target
bitgate-capabilitiesWhat the signed-in account may actually do
bitgate-actionA capability-gated button that explains refusals
bitgate-admin-panelEffective state, attribution, and controls

Policy presets

Set with the policy attribute.

NameProfilesFor
socialfeed, profile, playbackTimelines and posts. Discovery declines to hard-hide.
commercebrowse, detail, checkout, seller-dashboardMarketplaces. Checkout decides transactions only.
admin-onlydefaultOperator deny lists, with no trust-graph signals at all.
A gate threshold of 0 means disabled, not "fires on everything" — so zeroing a value is a safe way to switch it off.

Common mistakes

Recomputing policy in your views

If your renderer compares report counts to thresholds, the point has been lost. Consume the decision.

Reusing a listing-page verdict at checkout

Evaluate transaction-critical actions against their own profile at the moment of action. State moves.

Seeding trust before the viewer

setViewer clears viewer-scoped state by design. Set contacts, blocks, and overrides after it.

Exposing evidence everywhere

Public surfaces get counts; appeal surfaces get identities. exposeEvidence is off by default for a reason.

The full guide, including the adapter contract and every profile option, lives in docs/integration-guide.md.