{"id":49416198,"url":"https://github.com/mstrofnone/strfry-namecoin-policy","last_synced_at":"2026-04-29T03:07:04.036Z","repository":{"id":354372006,"uuid":"1215053028","full_name":"mstrofnone/strfry-namecoin-policy","owner":"mstrofnone","description":"strfry write-policy plugin: Namecoin .bit NIP-05 verification via ElectrumX","archived":false,"fork":false,"pushed_at":"2026-04-28T08:34:51.000Z","size":34,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-28T10:29:13.603Z","etag":null,"topics":["electrumx","namecoin","nip-05","nostr","strfry"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/mstrofnone.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2026-04-19T12:19:42.000Z","updated_at":"2026-04-28T08:34:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mstrofnone/strfry-namecoin-policy","commit_stats":null,"previous_names":["mstrofnone/strfry-namecoin-policy"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/mstrofnone/strfry-namecoin-policy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstrofnone%2Fstrfry-namecoin-policy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstrofnone%2Fstrfry-namecoin-policy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstrofnone%2Fstrfry-namecoin-policy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstrofnone%2Fstrfry-namecoin-policy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mstrofnone","download_url":"https://codeload.github.com/mstrofnone/strfry-namecoin-policy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstrofnone%2Fstrfry-namecoin-policy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32408464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T02:37:21.628Z","status":"ssl_error","status_checked_at":"2026-04-29T02:36:50.947Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["electrumx","namecoin","nip-05","nostr","strfry"],"created_at":"2026-04-29T03:07:02.115Z","updated_at":"2026-04-29T03:07:04.025Z","avatar_url":"https://github.com/mstrofnone.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# strfry-namecoin-policy\n\nA [**strfry**][strfry] write-policy plugin that verifies **Namecoin `.bit` NIP-05 identities**\nagainst the Namecoin blockchain (via ElectrumX) before accepting Nostr events.\n\n\u003e If a user publishes a kind:0 metadata event with `nip05: \"alice@example.bit\"`,\n\u003e this plugin looks up `d/example` on the Namecoin blockchain, extracts the\n\u003e pubkey for local part `alice`, and only accepts the event if it matches\n\u003e `event.pubkey`. Everything else gets a NIP-20 rejection.\n\nNamecoin NIP-05 is censorship-resistant identity: no DNS, no web server, no\nregistrar, no CA — just UTXOs on a public blockchain. This plugin is the\nserver-side counterpart to client support in [Amethyst][amethyst-pr] and\n[Nostur][nostur-pr].\n\n[strfry]: https://github.com/hoytech/strfry\n[amethyst-pr]: https://github.com/vitorpamplona/amethyst/pull/1914\n[nostur-pr]: https://github.com/nostur-com/nostur-ios-public/pull/60\n\n---\n\n## What it does\n\nFor every event strfry is about to store, this plugin decides:\n\n- **kind:0 (metadata)** — parses `content.nip05`:\n  - If it ends in `.bit` (or is `d/name` / `id/name`): resolves via\n    ElectrumX and requires `event.pubkey` to match.\n  - If it's a regular web NIP-05 (`alice@example.com`): passes through\n    unverified by default (this plugin is a Namecoin gate, not a web NIP-05\n    verifier). Set `NAMECOIN_POLICY_ALLOW_NON_BIT=false` to reject non-.bit\n    identities outright.\n  - If missing or unparsable: passes through.\n- **other kinds** — accepted by default. In strict mode\n  (`NAMECOIN_POLICY_MODE=all-kinds-require-bit`), only authors seen in a\n  verified `.bit` kind:0 during this process's lifetime are allowed.\n\nSuccessful resolutions are cached in-memory (LRU, configurable TTL) so a\nbusy relay doesn't hammer ElectrumX.\n\n## Requirements\n\n- Node.js **≥ 18** (uses only built-ins: `net`, `tls`, `readline`, `crypto`)\n- A reachable namecoin-ElectrumX server. Either run your own\n  (see the [deployment guide][deploy]) or use a public one such as\n  `electrumx.testls.space:50002`.\n\n[deploy]: https://github.com/namecoin/namecoin.org/pull/749\n\n\u003e **You do _not_ need to run `namecoind` on the relay host.** This plugin\n\u003e talks **only to ElectrumX** over TCP/TLS — it has no Namecoin Core / Bitcoin\n\u003e Core JSON-RPC dependency. See [Deployment topologies](#deployment-topologies)\n\u003e below.\n\n## Deployment topologies\n\nThis plugin is a thin ElectrumX client. It does not embed, spawn, or speak\nJSON-RPC to `namecoind`/`bitcoind`. The only thing it needs is a host:port\nthat answers the Electrum protocol (1.4) with a Namecoin-aware name index.\n\nThat gives you three sensible deployment shapes:\n\n### 1. External public ElectrumX (zero infra)\n\nPoint the plugin at a community-run namecoin-ElectrumX server such as\n`electrumx.testls.space:50002`. This is the simplest setup and is what the\nrepo's [examples/wrapper.sh](./examples/wrapper.sh) does by default.\n\n```sh\nexport NAMECOIN_ELECTRUMX_HOST=\"electrumx.testls.space\"\nexport NAMECOIN_ELECTRUMX_PORT=\"50002\"\nexport NAMECOIN_ELECTRUMX_TLS=\"true\"\n# Strongly recommended: pin the self-signed cert\nexport NAMECOIN_ELECTRUMX_CERT_PIN=\"\u003csha256-of-cert-DER\u003e\"\n```\n\nTrust model: you trust the operator not to lie about Namecoin name values.\nThey can withhold or stale-serve, but they cannot forge consensus.\n\n### 2. Your own ElectrumX (recommended for production relays)\n\nRun [namecoin-electrumx][nx] alongside your own [namecoind][nmc] — usually\non the same host or LAN as the strfry relay — and point the plugin at\n`localhost:50002`. The plugin still only speaks the Electrum protocol; the\n`namecoind` JSON-RPC link is between *ElectrumX and namecoind*, not between\nthis plugin and namecoind.\n\n```\n  ┌──────────────┐  stdio   ┌────────────┐  Electrum 1.4 (TCP/TLS)  ┌───────────┐\n  │   strfry     │ ◀───▶ │ this plug- │ ────────────────────▶ │ ElectrumX │\n  │ write-policy │        │    in      │                          │           │\n  └──────────────┘        └────────────┘                          └─────┬─────┘\n                                                                       │ JSON-RPC\n                                                                       ▼\n                                                              ┌──────────────┐\n                                                              │  namecoind   │\n                                                              │ (full node)  │\n                                                              └──────────────┘\n```\n\nThis is the highest-assurance option: nobody else's server is in the\nverification path.\n\n[nx]: https://github.com/namecoin/electrumx\n[nmc]: https://github.com/namecoin/namecoin-core\n\n### 3. Mixed / failover\n\nThe plugin opens one short-lived TCP/TLS connection per uncached resolve,\nso swapping the `NAMECOIN_ELECTRUMX_HOST` env var and restarting strfry is\nthe entire \"failover\" procedure. There is no built-in pool of servers; if\nyou want HA, run a TCP load balancer (e.g. HAProxy) in front of multiple\nElectrumX endpoints and point the plugin at the LB.\n\n### What this plugin does **not** do\n\n- ❌ It does **not** open a JSON-RPC connection to `namecoind`. There is no\n  `rpcuser`/`rpcpassword`/`rpcport` config, by design.\n- ❌ It does **not** require `blockchain.name.show` or any Namecoin-specific\n  ElectrumX extension. It uses only the generic Electrum methods\n  (`blockchain.scripthash.get_history`, `blockchain.transaction.get`,\n  `blockchain.headers.subscribe`) plus pure-JS Bitcoin script parsing —\n  same algorithm as Amethyst and Nostur. See [src/electrumx.js](./src/electrumx.js).\n- ❌ It does **not** validate SPV proofs. The trust model is \"trust the\n  ElectrumX server not to serve stale/wrong values\" — see\n  [Security notes](#security-notes) for mitigations (cert pinning, run your\n  own server).\n\n## Install\n\n```bash\nnpm install -g strfry-namecoin-policy\n```\n\nOr from source:\n\n```bash\ngit clone https://github.com/mstrofnone/strfry-namecoin-policy\ncd strfry-namecoin-policy\nnpm test   # unit tests, hermetic\n```\n\nThere are **no runtime dependencies** — just vendor the repo directly\nwherever strfry runs.\n\n## Configure strfry\n\nstrfry's `writePolicy.plugin` directive is a single command path. Since we\nneed to pass env vars to the plugin, wrap it in a tiny shell script:\n\n**`/opt/strfry/plugins/strfry-namecoin-policy-wrapper.sh`**\n\n```sh\n#!/bin/sh\nexport NAMECOIN_ELECTRUMX_HOST=\"electrumx.testls.space\"\nexport NAMECOIN_ELECTRUMX_PORT=\"50002\"\nexport NAMECOIN_POLICY_MODE=\"kind0-only\"\nexec /usr/bin/env node /usr/local/lib/node_modules/strfry-namecoin-policy/bin/strfry-namecoin-policy.js\n```\n\nThen in **`strfry.conf`**:\n\n```\nrelay {\n    writePolicy {\n        plugin = \"/opt/strfry/plugins/strfry-namecoin-policy-wrapper.sh\"\n    }\n}\n```\n\nMake both files executable and restart strfry. A full example is in\n[`examples/strfry.conf.snippet`](examples/strfry.conf.snippet) and\n[`examples/wrapper.sh`](examples/wrapper.sh).\n\n## Configuration (environment variables)\n\n| Variable | Default | Description |\n|---|---|---|\n| `NAMECOIN_ELECTRUMX_HOST` | *(required)* | Hostname of the ElectrumX server. **Without it, the plugin fails closed by default** — see `NAMECOIN_POLICY_SOFT_FAIL`. |\n| `NAMECOIN_ELECTRUMX_PORT` | `50002` (TLS) / `50001` (TCP) | TCP port. |\n| `NAMECOIN_ELECTRUMX_TLS` | `true` | Use TLS (`true`/`false`). |\n| `NAMECOIN_ELECTRUMX_CERT_PIN` | — | One or more cert pins (comma-separated for rotation). Each pin is either a 64-hex SHA-256 of the DER cert, or `sha256/\u003cbase64\u003e` for a SubjectPublicKeyInfo pin. When set, the system trust store is bypassed and only matching certs are accepted. |\n| `NAMECOIN_ELECTRUMX_INSECURE` | `false` | If `true`, disable TLS verification entirely. **For testing only** — production setups should pin a cert instead. Triggers a loud startup banner. |\n| `NAMECOIN_ELECTRUMX_TIMEOUT_MS` | `5000` | Per-query timeout. |\n| `NAMECOIN_ELECTRUMX_RETRIES` | `2` | Retry attempts per lookup on connect/parse/IO failure. |\n| `NAMECOIN_POLICY_MODE` | `kind0-only` | `kind0-only`: verify only kind:0 with `.bit` NIP-05. `all-kinds-require-bit`: reject all events from authors that haven't been verified yet. |\n| `NAMECOIN_POLICY_CACHE_TTL_MS` | `300000` | TTL for resolver cache (ms). |\n| `NAMECOIN_POLICY_LOG_LEVEL` | `info` | `silent` / `info` / `debug`. Logs go to stderr and are captured by strfry. |\n| `NAMECOIN_POLICY_ALLOW_NON_BIT` | `true` | If `false`, kind:0 events with non-`.bit` NIP-05 identifiers are rejected. |\n| `NAMECOIN_POLICY_MIN_CONFIRMATIONS` | `1` | Minimum confirmations a Namecoin name-update tx must have before it's trusted. `0` allows mempool/unconfirmed tx (testing only). Higher values harden against reorgs and malicious-server fabrication at the cost of slightly slower propagation. |\n| `NAMECOIN_POLICY_NEG_CACHE_TTL_MS` | `30000` | Short TTL for *parse-failure* negatives (malformed JSON, missing `nostr` key, ElectrumX returned null). Successful negatives — e.g. `bob@x.bit` looking up a record that lists only `_` and `alice` — still use the long `NAMECOIN_POLICY_CACHE_TTL_MS`. |\n| `NAMECOIN_POLICY_LOOKUP_RPS` | `5` | Sustained ElectrumX lookup rate (tokens/sec). Cache hits don't count. |\n| `NAMECOIN_POLICY_LOOKUP_BURST` | `10` | Max burst size for ElectrumX lookups. |\n| `NAMECOIN_POLICY_LOOKUP_QUEUE_MS` | `2000` | Max time (ms) a single lookup will wait for a token before returning a `rate-limited:` reject. |\n| `NAMECOIN_POLICY_SOFT_FAIL` | `false` | If `true` and `NAMECOIN_ELECTRUMX_HOST` is unset, accept all events without verification (legacy behavior). Default fails closed. |\n\n### Cert pin formats\n\n`NAMECOIN_ELECTRUMX_CERT_PIN` accepts two pin shapes, and any number of\nthem comma-separated (any-match):\n\n- **DER fingerprint** — `sha256(DER(cert))` as 64 hex chars. Simple and\n  matches what `openssl x509 -outform der | sha256sum` prints, but breaks\n  when the cert rotates even if the underlying key is reused.\n- **SPKI pin** — `sha256/\u003cbase64-of-sha256(SubjectPublicKeyInfo)\u003e`. Survives\n  cert rotation as long as the operator keeps the same keypair, which is\n  the right default for long-lived self-signed ElectrumX servers. Compute\n  with: `openssl s_client -connect host:50002 \u003c/dev/null 2\u003e/dev/null |\n  openssl x509 -pubkey -noout | openssl pkey -pubin -outform der |\n  openssl dgst -sha256 -binary | base64`. Configure as e.g.\n  `NAMECOIN_ELECTRUMX_CERT_PIN=\"sha256/AAAA...=,sha256/BBBB...=\"` to allow\n  a current and a next-rotation key simultaneously.\n| `NAMECOIN_ELECTRUMX_HOSTS` | — | Comma-separated `host:port[,host:port,…]` list. Overrides `*_HOST/*_PORT` when set. Round-robin with per-host circuit breaker (30s open, exp backoff to 5min). See [Operational](#operational). |\n| `NAMECOIN_ELECTRUMX_SOCKS5` | — | `host:port` of a no-auth SOCKS5 proxy (e.g. `127.0.0.1:9050` for Tor). DNS is delegated to the proxy. |\n| `NAMECOIN_POLICY_CACHE_PATH` | — | If set, both the resolver cache and verified-author set persist to this file. Uses `better-sqlite3` (optional dep) when available, falls back to a JSONL append-log otherwise. |\n| `NAMECOIN_POLICY_METRICS_PORT` | `0` | If non-zero, expose Prometheus metrics on `127.0.0.1:\u003cport\u003e/metrics`. Always bound to localhost. |\n| `NAMECOIN_POLICY_POOL_KEEPALIVE_MS` | `30000` | Idle timeout for the warm ElectrumX connection pool. Set to `0` to use one-connection-per-resolve mode. |\n\n## Operational\n\nFor higher-traffic relays, the plugin supports several operational\nfeatures that you can opt into via environment variables. All are\noff-by-default — the legacy single-host, per-resolve-connection,\nin-memory-cache behavior is preserved when these are unset.\n\n| Feature | Env var(s) | Example |\n|---|---|---|\n| Persistent cache | `NAMECOIN_POLICY_CACHE_PATH` | `NAMECOIN_POLICY_CACHE_PATH=/var/cache/strfry-namecoin/cache.db` |\n| Prometheus metrics | `NAMECOIN_POLICY_METRICS_PORT` | `NAMECOIN_POLICY_METRICS_PORT=9091` (curl `http://127.0.0.1:9091/metrics`) |\n| SOCKS5 / Tor | `NAMECOIN_ELECTRUMX_SOCKS5` | `NAMECOIN_ELECTRUMX_SOCKS5=127.0.0.1:9050` |\n| Connection pool | `NAMECOIN_POLICY_POOL_KEEPALIVE_MS` | `NAMECOIN_POLICY_POOL_KEEPALIVE_MS=60000` (`0` = disable) |\n| Multi-ElectrumX + circuit breaker | `NAMECOIN_ELECTRUMX_HOSTS` | `NAMECOIN_ELECTRUMX_HOSTS=ex1.example:50002,ex2.example:50002` |\n| Happy-eyeballs IPv6/IPv4 | *(automatic)* | When the host has both A and AAAA records, both are tried with a 250 ms stagger; the first connect wins. Skipped when SOCKS5 is enabled (proxy resolves names). |\n\nMetrics exported (Prometheus textfile format):\n\n- Counters: `lookups_total`, `cache_hits_total`, `cache_misses_total`,\n  `acceptances_total`, `rejections_total{reason=\"…\"}`,\n  `electrumx_errors_total{type=\"timeout|socket|tls|cert-pin|parse|closed|socks5|dns|refused|unreachable|other\"}`\n- Histogram: `lookup_duration_ms` with buckets\n  `[10, 50, 100, 250, 500, 1000, 2500, 5000, +Inf]`\n\nThe metrics listener binds to `127.0.0.1` only — expose it externally\nvia an explicit reverse proxy if you need remote scraping.\n\nThe persistent cache splits into two sqlite namespaces in one file\n(`resolver` for name lookups, `verifiedAuthors` for the\n`all-kinds-require-bit` mode). If `better-sqlite3` is not installed\n(it's an `optionalDependencies` entry, so `npm install` won't fail if\nit can't build), we silently fall back to a JSONL append-log with\nperiodic in-place compaction. If the cache file can't be opened at\nall, we log once and fall back to in-memory — a disk problem will\nnever take the relay offline.\n\nThe circuit breaker keeps each ElectrumX host in `closed` (healthy),\n`open` (recent failure, skip for cooldown), or `half-open` (one probe\nslot) state. Failures double the cooldown (30 s → 1 min → 2 min → 4\nmin → 5 min cap); successes reset it. If every configured host is\nopen, the plugin still round-robins through them — better to attempt\na flapping ElectrumX than to soft-accept every event without\nverification.\n\n## Supported identifier forms\n\n| User input in `nip05` | Namecoin name looked up | Local part |\n|---|---|---|\n| `alice@example.bit` | `d/example` | `alice` |\n| `_@example.bit` | `d/example` | `_` |\n| `example.bit` | `d/example` | `_` |\n| `d/example` | `d/example` | `_` |\n| `id/alice` | `id/alice` | `_` |\n\n## Supported Namecoin value formats\n\nInside the Namecoin name's JSON value, the plugin accepts:\n\n```json\n// domain namespace, single user\n{\"nostr\": \"\u003c64-char-hex-pubkey\u003e\"}\n```\n\n```json\n// domain namespace, multi-user with relay hints\n{\n  \"nostr\": {\n    \"names\":  { \"_\": \"\u003chex\u003e\", \"alice\": \"\u003chex\u003e\", \"bob\": \"\u003chex\u003e\" },\n    \"relays\": { \"\u003chex\u003e\": [\"wss://relay.example.com\"] }\n  }\n}\n```\n\n```json\n// identity namespace\n{\"nostr\": \"\u003chex\u003e\"}\n// or\n{\"nostr\": {\"pubkey\": \"\u003chex\u003e\", \"relays\": [\"wss://...\"]}}\n```\n\n## Tests\n\n```bash\nnpm test                 # hermetic unit tests (no network)\nLIVE_ELECTRUMX=1 \\\n  NAMECOIN_ELECTRUMX_HOST=electrumx.testls.space \\\n  NAMECOIN_ELECTRUMX_PORT=50002 \\\n  node test/live.js      # hit a real namecoin-ElectrumX server\n```\n\n## Security notes\n\n- **TLS cert pinning.** The common public namecoin-ElectrumX servers use\n  self-signed certificates. Without pinning you must disable TLS\n  verification — don't. Use `NAMECOIN_ELECTRUMX_CERT_PIN` to pin the\n  server's SHA-256 DER fingerprint. Example: grab the fingerprint with\n  `openssl s_client -connect host:50002 -showcerts \u003c/dev/null 2\u003e/dev/null |\n   openssl x509 -outform der | sha256sum`.\n- **Server trust.** ElectrumX can lie about recent blocks but can't forge\n  Namecoin consensus. A malicious server could return stale data\n  (pre-update) — this is acceptable for name resolution because Namecoin\n  updates are infrequent and a defender can rotate servers. For maximum\n  assurance, run your own ElectrumX against your own namecoind.\n- **Cache poisoning.** The cache is in-memory and keyed by identifier; it\n  does not cross processes and is cleared on restart. Negative results are\n  cached (to stop attackers from spamming lookups with garbage names) but\n  network errors are **not** cached.\n- **No cryptographic signature check.** strfry has already validated\n  `event.id` and `event.sig` by the time this plugin sees the event; we\n  simply trust the pubkey.\n\n## Limitations / known gaps\n\n- Resolution uses the same scripthash-based algorithm as Amethyst and\n  Nostur — `blockchain.scripthash.get_history` + `blockchain.transaction.get`\n  + NAME_UPDATE script parsing — so it works against **any** namecoin-aware\n  ElectrumX (no non-standard `blockchain.name.show` extension required).\n  See [`src/electrumx.js`](src/electrumx.js) for the full protocol.\n- No SPV-proof verification. We trust the server's response. A malicious\n  or stale server could (in principle) lie about the name's current\n  value, but can't forge Namecoin consensus.\n- `all-kinds-require-bit` only remembers verified authors for the lifetime\n  of the plugin process. If strfry restarts the plugin, the cache\n  rebuilds as events flow in again.\n- Single plugin slot. strfry currently supports one write-policy plugin\n  at a time; chain this with other policies by writing a meta-plugin\n  (there are examples upstream) or by contributing to the upstream\n  router.\n\n[nmcex]: https://github.com/namecoin/electrumx\n\n## How it resolves a `.bit` name\n\n```\nalice@example.bit\n    │\n    ▼\n1. parse           → namecoinName=\"d/example\", localPart=\"alice\"\n2. build script    → OP_NAME_UPDATE \u003cpush(\"d/example\")\u003e \u003cpush(\"\")\u003e OP_2DROP OP_DROP OP_RETURN\n3. scripthash      → hex(reverse(SHA-256(script)))\n4. get_history     → [{tx_hash, height}, …]   ← last entry wins\n5. transaction.get → scan vouts for script starting with 0x53 (OP_NAME_UPDATE) or 0x52 (OP_NAME_FIRSTUPDATE)\n6. parse script    → extract JSON value from the second push-data item\n7. check expiry    → current_tip - update_height must be \u003c 36 000\n8. extract pubkey  → value.nostr.names[\"alice\"]\n```\n\nResult is cached and compared against `event.pubkey` on every new kind:0\nfrom that identifier. Cache hits avoid the round-trip entirely.\n\n## Development\n\n```bash\n# Run all unit tests\nnpm test\n\n# Run the end-to-end plugin test (spawns the binary)\nnode --test test/plugin.test.js\n```\n\nThe code is deliberately small (~500 lines of Node, no deps) so it's easy\nto audit before running inside your relay.\n\n## License\n\n[MIT](LICENSE) © mstrofnone\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmstrofnone%2Fstrfry-namecoin-policy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmstrofnone%2Fstrfry-namecoin-policy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmstrofnone%2Fstrfry-namecoin-policy/lists"}