402-announce

中文文档
2.3.1发布于 2026/10/18MITTypeScriptES Module构建来源已验证近 30 天下载 2063

在Nostr上公布HTTP 402服务(L402和x402),用于分权发现. Kind 31402参数化可替换事件.

中文使用文档
文档目录23 节
  1. 如何运作
  2. 快开始
  3. 如何融为一体
  4. 它会做什么
  5. 它不会做的
  6. API 密码
  7. announceService(config): Promise<Announcement>
  8. buildAnnounceEvent(secretKey, config): VerifiedEvent
  9. isPrivateHost(hostname): boolean
  10. 常数和类型
  11. 配置选项
  12. URL和中继验证
  13. 存储通知 API
  14. 宣布流量
  15. 事件格式
  16. 标记
  17. 公认付款方法铁路
  18. 内容
  19. 多个 URL 对多个事件
  20. 警卫
  21. 生态系统
  22. ForgeSworn 工具包的一部分
  23. 许可证

402发通知

实验存储发现 现在有一个单独的,没有钥匙的API 对于公众付费 包和免费镜像掩蔽。 见 第1版合同, (中文(简体) ), 杰森·斯克马 财务报告和审定财务报表 已签名的互操作性固定装置。 。 。 . buildStorageAnnouncement 为外部签名者创建未签名的事件; acceptSignedStorageAnnouncement 核实准确的返回事件 selectStorageAnnouncements 保存已过期/已撤销的替换记录. 这些API不发布,联系存储节点或购买存储. 现有 已支付 API announceService 合同不变.

诺斯特: npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2

在Nostr上宣布HTTP 402服务 用于分散的发现. 支持L402和x402支付协议.

CI Coverage MIT Licence GitHub Sponsors

出版物 类型31402 参数化可替换的事件,以便AI代理(和任何诺斯特客户端)可以在没有中央注册的情况下发现付费API.

活盘 立刻在网络上公布.

如何运作

flowchart LR
    A[Your API] -->|"npm install<br/>402-announce"| B[402-announce]
    B -->|"kind 31402<br/>signed event"| C[(Nostr Relays)]
    C -->|subscribe & filter| D[402-mcp]
    D -->|discover + pay| A

    style B fill:#f59e0b,color:#000
    style C fill:#8b5cf6,color:#fff
    style D fill:#3b82f6,color:#fff

您的API 向 Nostr 中继器发布服务公告 。 人工智能特工(通过 402-mcp (中文(简体) )发现后支付发票,消费API. 无需中央登记处.

快开始

npm install 402-announce

402 通知只是无害环境管理。 它的船没有共同JS的建造及其 exports 地图没有 require 条件,所以 require('402-announce') 失败 ERR_PACKAGE_PATH_NOT_EXPORTED启用 import,或 await import('402-announce') 从普通JS.

import { announceService } from '402-announce'

const handle = await announceService({
  secretKey: '64-char-hex-nostr-secret-key',
  relays: ['wss://relay.damus.io', 'wss://relay.primal.net'],
  identifier: 'jokes-api',
  name: 'Jokes API',
  urls: ['https://jokes.example.com'],
  about: 'A joke-telling service behind an L402 paywall',
  pricing: [
    { capability: 'get_joke', price: 1, currency: 'sats' },
  ],
  paymentMethods: [['l402', 'lightning'], ['cashu'], ['xcashu']],
  topics: ['comedy', 'ai'],
  capabilities: [
    { name: 'get_joke', description: 'Returns a random joke' },
  ],
  version: '1.0.0',
})

console.log('Published event:', handle.eventId)
console.log('From pubkey:', handle.pubkey)

// Later, when shutting down:
handle.close()

多种运输实例 (清网+Tor + 握手-相同服务,不同接入路径):

const handle = await announceService({
  secretKey: '64-char-hex-nostr-secret-key',
  relays: ['wss://relay.damus.io', 'wss://relay.primal.net'],
  identifier: 'jokes-api',
  name: 'Jokes API',
  urls: [
    'https://jokes.example.com',                  // clearnet
    'http://jokesxyz...onion',                    // Tor hidden service
    'https://jokes.example.hns',                  // Handshake domain
  ],
  about: 'A joke-telling service behind an L402 paywall',
  pricing: [{ capability: 'get_joke', price: 1, currency: 'sats' }],
  paymentMethods: [['l402', 'lightning']],
  topics: ['comedy', 'ai'],
})

每个 URL 成为单独的 url 标记于 31402 类事件。 客户端按顺序尝试,并使用可以达到的任意方法.

urls 接受1至10个条目。 并不是每个可解析的 URL 被接受: 见 URL和中继验证。 。 。 .

见 examples/ gitHub 仓库中可运行脚本的目录。 它不包含在 npm 包中 .

如何融为一体

flowchart LR
    subgraph "Your Server"
        API[Your API]
        TB[toll-booth<br/>L402 paywall]
        AN[402-announce]
    end

    subgraph "Nostr Network"
        R1[(Relay 1)]
        R2[(Relay 2)]
    end

    subgraph "AI Agent"
        MCP[402-mcp<br/>discovery + payment]
    end

    API --> TB
    TB -->|"protects"| API
    AN -->|"kind 31402"| R1
    AN -->|"kind 31402"| R2
    R1 -->|"subscribe"| MCP
    R2 -->|"subscribe"| MCP
    MCP -->|"HTTP + L402 token"| TB

    style TB fill:#ef4444,color:#fff
    style AN fill:#f59e0b,color:#000
    style MCP fill:#3b82f6,color:#fff
  1. 收费 用 L402 付费墙包裹您的 API
  2. 402发通知 发布描述服务、定价和支付方法的31402类事件
  3. 402-mcp (中文(简体) ) 发现通知,支付发票,并拨打你的API

它会做什么

  • 构建和标志 31402 诺斯特事件
  • 以平行方式发布给一个或多个 Nostr 中继器
  • 使用后 Zeroise 密钥字节
  • 当单个接力失败时(如果每次接力失败,仍能解决;见 announceService(中文(简体) )
  • 规定 a close() 干净断开的控件

它不会做的

API 密码

announceService(config): Promise<Announcement>

高级功能,用于构建,签名,并发布事件给多个中继器.

回返 a 一个 Announcement 控件 :

  • eventId: 已签名的诺斯特事件的ID
  • pubkey: 从您的密钥中衍生出来的诺斯特公钥
  • close(): 断开所有中继器( 同步; 在关闭时调用, 以避免泄露 WebSockets)

拒绝 (这是一个 async 当配置无效时: a secretKey 这不是64个十六进制字符, 一个空接力列表, 超过50个接力, 一个不是的接力 URL ws:// 或者说 wss://,在私人或回放主机、服务中拥有证书或分数 urls 列表中没有公共 URL,或者任何 buildAnnounceEvent 验证失败.

中继失败永远不会拒绝. 每个中继器都与10秒的连接超时同时进行试验,并记录故障 console.warn如果每次中继失败, announceService 仍然决定与 eventId 和日志 [402-announce] No relays accepted the event编辑 Announcement handle不报告有多少继电器接受事件,所以警告是唯一的信号. 如果您需要知道至少一个中继存储了事件,请拨 buildAnnounceEvent 然后自己出版,检查每个 relay.publish() 结果.

buildAnnounceEvent(secretKey, config): VerifiedEvent

下层功能,用于构建和签名事件而不发布. 如果你自己管理中继连接,那就有用了. config 是一个 AnnounceConfig 无 relays。它会同步投放无效输入,不应用“至少一个公共URL”规则.

isPrivateHost(hostname): boolean

SSRF负责保护 announceService 适用于转发和服务 URL,导出为您自己使用。 它需要一个赤裸的主机名( 不是 URL) 然后返回 true 用于:

  • localhost, (中文(简体) ), *.localhost
  • IPv6 请检查url=值 (帮助) ::1, (中文(简体) ), ::链接 - 本地 fe80::/10,独特的本地 fc00::/7,泰雷多语 2001:0000::/32,和 IPv4-映射, IPv4-相容或 6to4 形式嵌入私人 IPv4 地址
  • IPv4 请检查url=值 (帮助) 0.0.0.0/8, (中文(简体) ), 10.0.0.0/8, (中文(简体) ), 100.64.0.0/10, (中文(简体) ), 127.0.0.0/8, (中文(简体) ), 169.254.0.0/16, (中文(简体) ), 172.16.0.0/12, (中文(简体) ), 192.0.0.0/24, (中文(简体) ), 192.0.2.0/24, (中文(简体) ), 192.88.99.0/24, (中文(简体) ), 192.168.0.0/16, (中文(简体) ), 198.18.0.0/15, (中文(简体) ), 198.51.100.0/24, (中文(简体) ), 203.0.113.0/24 财务报告和审定财务报表 240.0.0.0/4
  • 八进制,十六进制,单整数和两相或三相接的接头IPv4拼写法(例如: 0x7f000001, (中文(简体) ), 2130706433, (中文(简体) ), 127.1(中文(简体) )

它只检查字符串,从来不解决DNS. 它不标出多播地址, 例如 : 224.0.0.1。 。 。 .

常数和类型

  • L402_ANNOUNCE_KIND:事件的类型, 31402
  • AnnounceConfig, (中文(简体) ), Announcement, (中文(简体) ), PricingDef, (中文(简体) ), CapabilityDef: 纯类型导出
interface PricingDef {
  capability: string // 1-64 characters, no control characters
  price: number      // finite and non-negative
  currency: string   // any non-blank string up to 32 characters, no control characters
}

interface CapabilityDef {
  name: string         // up to 128 characters
  description: string  // up to 4096 characters
  endpoint?: string    // starts with '/', 'http://' or 'https://'; up to 2048 characters
  schema?: unknown     // JSON Schema for the input
  outputSchema?: unknown // JSON Schema for the output
}

currency 是一个自由形式的标签,而不是一个引用: 库不对照允许的值列表来检查,以及 'sats' 仅是整个文档中使用的价值。 客户是否理解另一个单位,取决于该客户. pricing 需要1至100个条目和 capabilities 最多100个。 整个事件内容可能达到最多64个KiB和20个等级的筑巢.

配置选项

外地类型需求说明
secretKeystring对64 Char 十六进制鼻音密钥
relaysstring[]对中继 URL (E)wss:// 或者说 ws://(中文(简体) )
identifierstring对独一无二的上市ID( Nostr) d 标签)
namestring对可读服务名称
urlsstring[]对服务运输端点(1-10个独特的条目,每个条目最多可达2048个字符;一些方案被拒绝 announceService 需要一个公开的 URL,参见 URL和中继验证(中文(简体) )
aboutstring对简短说明
pricingPricingDef[]对个人能力定价
paymentMethodsstring[][]对接受付款方法(每个条目为: [rail, ...params](中文(简体) )
picturestring没有图标 URL
topicsstring[]没有过滤的主题标签
capabilitiesCapabilityDef[]没有能力细节(存储于事件内容)
versionstring没有服务版本( 存储于事件内容)

URL和中继验证

两者 buildAnnounceEvent 财务报告和审定财务报表 announceService 拒绝一个 urls 条目:

  • 无法解析 new URL(),大于2048个字符或包含控件字符
  • 使用此方案 data:, (中文(简体) ), file:, (中文(简体) ), javascript:, (中文(简体) ), blob: 或者说 vbscript:
  • 复制另一个条目

buildAnnounceEvent 停止那里并接受私人主机,因为它只是串行. announceService 还需要至少一个公共 URL: a http: 或者说 https: 主机名的 URL isPrivateHost 不符, a .onion 主机名,或任何其他方案(后两个跳过主机检查). 其余的条目可能是私人的.

中继 URL 更为严格 : ws:// 或者说 wss:// 只有,没有嵌入式证书,没有私有或回回机主机,最多在去除后50个. A 类 ws:// 中继记录一个警告.

这些检查只读取主机名文本 。 没有DNS分辨率,因此DNS重新绑定没有被捕获;部署在网络层的Egress控制后.

存储通知 API

此 README 顶部描述的存储扩展是无密钥的: 它构建未签名的事件, 验证外部签名器返回的事件, 并在它们之间选择 。 它不发布、联系存储节点或购买存储,而是与 announceService。不要将存储描述传递给 announceService 或者说 buildAnnounceEvent。 。 。 .

导入路径 。 这6个功能从 402-announce/storage 子路径, 也来自套件根 :

import { buildStorageAnnouncement } from '402-announce/storage'
// or: import { buildStorageAnnouncement } from '402-announce'

子路径只包含六个功能和 ParsedStorageAnnouncement 类型。 常数( E) :STORAGE_PROFILE, (中文(简体) ), STORAGE_SERVICE_PATH, (中文(简体) ), STORAGE_MAX_EVENT_BYTES, (中文(简体) ), STORAGE_MAX_TTL_SECONDS, (中文(简体) ), STORAGE_DEFAULT_MAX_AGE_SECONDS说明类型(例如: StorageServiceDescription 财务报告和审定财务报表 StorageReadOptions)只从根导出.

函数签署目的
buildStorageAnnouncement(value: unknown, createdAt?: number) => EventTemplate验证一个描述并返回一个外部签名符的未签名的 31402 事件
acceptSignedStorageAnnouncement(value: unknown, requested: EventTemplate, options: StorageReadOptions) => ParsedStorageAnnouncement校验返回的事件, 并且只有在它与当前事件相同时才接受它 requested (同样善良, created_at,标签和内容)
parseStorageAnnouncement(value: unknown, options: StorageReadOptions) => ParsedStorageAnnouncement校验一个签名的事件, 并返回其中的描述和 a freshness 联 合 国 'current', (中文(简体) ), 'stale' 或者说 'expired'
selectStorageAnnouncements(values: readonly unknown[], options: StorageReadOptions) => ParsedStorageAnnouncement[]保持每个小酒鬼最新的有效记录 d 标记,包括已过期和已撤回的;无效的记录被跳过
validateStorageDescription(value: unknown) => StorageServiceDescription严格验证和复制一个公开的 HTTP 描述
validateStorageEndpointBinding(value: unknown, announcement: ParsedStorageAnnouncement, selectedOrigin: string, options: StorageReadOptions) => StorageServiceDescription对照签名的身份、列名和选定来源检查新获取的描述

StorageReadOptions 实值 { now: number; maxAgeSeconds?: number; clockSkewSeconds?: number },与 now 在Unix秒内。 函数抛出 Error('Invalid storage service profile.') 输入无效. selectStorageAnnouncements 跳过无效的记录取而代之,但仍为无效选项丢出或超过512个记录.

流量为构建,对外签名,接受,选择,再绑定到终点. 下面的例子使用一个丢弃的本地密钥作为外部签名器的站口(nostr-tools 是此软件包的依赖性 。 在生产中,密钥从未输入此代码 .

import { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools/pure'
import {
  acceptSignedStorageAnnouncement,
  buildStorageAnnouncement,
  selectStorageAnnouncements,
  validateStorageEndpointBinding,
} from '402-announce/storage'

const now = Math.floor(Date.now() / 1000)
const signerKey = generateSecretKey() // stand-in for an external signer

const description = {
  profile: 'forgesworn-storage-service-v1',
  service_pubkey: getPublicKey(signerKey),
  identifier: 'open-shelter-node',
  name: 'Open shelter node',
  about: 'Best-effort mirror shelter for signed blobs.',
  origins: ['https://storage.example/'],
  max_blob_bytes: 1073741824,
  observed_at: now,
  expires_at: now + 7200,
  state: 'published',
  checkout: null,
  services: [{
    id: 'open-shelter',
    type: 'free-mirror',
    revision: 1,
    availability: 'accepting',
    operation: 'mirror',
    authorisation: 'bud-11',
    retention: 'best-effort',
    eviction_policy: 'Guest mirrors can be evicted under storage pressure.',
  }],
}

// 1. Build the unsigned event. No key or network access.
const requested = buildStorageAnnouncement(description)

// 2. Sign it externally. The signer must not change the template.
const signed = finalizeEvent(requested, signerKey)

// 3. Accept the returned event. Throws if the signature, freshness or template differ.
const accepted = acceptSignedStorageAnnouncement(signed, requested, { now })
// Publishing accepted.event to relays is a separate step that you perform.

// 4. Later, a reader selects the newest record per listing from relay results.
const listings = selectStorageAnnouncements([accepted.event], { now })
const [listing] = listings
// Keep `listings` as watermarks. Show only current, published, accepting services.

// 5. After separately fetching the node's description (GET /services/v1, here reusing
//    `description`), check it matches the signed listing and the chosen origin.
const fetched = description
const current = validateStorageEndpointBinding(fetched, listing, 'https://storage.example/', { now })
console.log(current.identifier)

支付存储列表 checkout 对象和 paid-storage 服务。 见 第1版合同 每个字段和 签名固定装置 已支付、合并、撤回和过期的例子.

宣布流量

flowchart TD
    A[announceService config] --> B{Validate}
    B -->|"secretKey: 64-char hex"| C[Derive pubkey]
    B -->|"relays: wss:// URLs"| C
    C --> D[Build kind 31402 event]
    D --> E[Sign with secret key]
    E --> F[Zeroise key bytes]
    F --> G["Connect relays (parallel, 10s timeout)"]
    G --> H{Each relay}
    H -->|success| I[Publish event]
    H -->|failure| J[Log warning, continue]
    I --> K["Return { eventId, pubkey, close() }"]
    J --> K

事件格式

每个通知都是 类型31402 参数化可替换事件。 组合 pubkey + 键 d 标记独有地识别列表——再次以相同值发布更新了现有的列表.

graph TB
    subgraph "Kind 31402 Event"
        direction TB
        subgraph Tags
            D["d: jokes-api"]
            N["name: Jokes API"]
            U1["url: https://jokes.example.com"]
            U2["url: https://jokesapi.example.onion (optional)"]
            AB["about: A joke-telling service"]
            PMI1["pmi: l402, lightning"]
            PMI2["pmi: cashu"]
            P["price: get_joke, 1, sats"]
            T["t: comedy"]
        end
        subgraph Content["Content (JSON)"]
            CAP["capabilities: [{ name, description }]"]
            VER["version: 1.0.0"]
        end
    end

    style Tags fill:#1e293b,color:#e2e8f0
    style Content fill:#1e293b,color:#e2e8f0

标记

标记需求说明示例
d对此列表的独特标识符jokes-api
name对可读服务名称Jokes API
url对HTTP 端点( 每个 URL 一个标签; 可重复)https://jokes.example.com
about对简短说明A joke-telling service
pmi对支付方法标识符(可重复、多要素)l402, (中文(简体) ), lightning
price对能力定价(可重复)get_joke, (中文(简体) ), 1, (中文(简体) ), sats
t没有搜索/过滤的主题标签( 可重复)comedy
picture没有图标 URLhttps://example.com/icon.png

公认付款方法铁路

每个 paymentMethods 条目是第一个元素是铁路标识符,随后的元素是铁路专用参数的数组.

铁路示例说明
l402['l402', 'lightning']L402协议(照明BOLT-11发票)
cashu['cashu']现金(普通)
xcashu['xcashu']Cashu 电子现金通过 NUT-24(X-Cashu 头)
lnurlcash['lnurlcash', 'mint.example']在X-LNURLCASH标题中呈现的LUD-25无记名纸条(随后的元素是被接受的薄荷主机)
x402['x402', 'base', 'usdc', '0xabc...']x402 稳定币付款( 链、 令牌、 接收地址)
payment['payment', 'lightning']IETF 支付协议

内容

事件内容是一个带有可选字段的JSON对象. 每种能力也可携带 endpoint, (中文(简体) ), schema 财务报告和审定财务报表 outputSchema (见 CapabilityDef 上文:

{
  "capabilities": [
    { "name": "get_joke", "description": "Returns a random joke", "endpoint": "/api/joke" }
  ],
  "version": "1.0.0"
}

多个 URL 对多个事件

这种区分对操作者很重要:

一次事件的多个 URL ——使用 urls: ['...', '...'] 当 URL 代表 同一服务 不同运输方式(清网、Tor、握手)。 定价、证书和马卡龙签名密钥相同。 客户端选择可以达到的URL 。 这是为了抵制审查 和冗余.

单独的31402事件 ——发布新活动(不同) identifier当你有 真正不同的服务:每辆运输不同的定价等级,不同的能力,或独立运营的服务. 单一事件应描述一种逻辑服务.

简而言之:同一服务+不同的网络路径 + 一个有多个事件 url 标签。 不同的服务——单独的活动.

警卫

  • 密钥字节在签名后立即被零化( 在两个字节中) announceService 财务报告和审定财务报表 buildAnnounceEvent(中文(简体) )
  • 永远不要登录或坚持秘密密钥——让图书馆处理清理
  • 中继连接使用10秒超时防止挂起
  • 单个接力故障被记录为警告,但不要拒绝承诺. 如果每一个中继失败, announceService 仍然决定与 eventId 和日志 [402-announce] No relays accepted the event
  • 以私有模式和回回模式检查中继和服务 URL(见 URL和中继验证); DNS 重新绑定没有被捕获

生态系统

软件包目的
收费L402 中间软件——任何API在几分钟内成为收费站
罚单Bridge – 宣布一个功能呼叫的收费站服务
配置门提供闪电和Cashu支助的生产L402网关
402-mcp (中文(简体) )MCP 服务器,供 AI 代理商发现、支付和消费 402 API
活盘查看网络上宣布的每个服务

ForgeSworn 工具包的一部分

福吉斯沃恩语Name 为诺斯特建立开源加密身份,支付和协调工具.

图书馆它会做什么
树决定性的子标识衍生
环西格秒数256k1上的 SAG/LSAG 环签名
区域防护佩德森承诺范围证明
金丝雀胁迫性口语核查
手语人称核查标志
收费L402 支付中间软件
土拨鼠覆盖多边形的Geohash工具包
节点证明NIP-VA可核查的证明
统治权基于 Epoch 的加密访问控制
鼻孔微风隐私保护信任网

许可证

麻省理工学院

版本与发布记录

402-announce 共发布 29 个版本,首次发布于 2026/03/15;过去 12 个月发布了 29 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.3.1latest2026/10/1820 天前
2.3.02026/10/1226 天前
2.2.02026/08/222 个月前
2.1.12026/04/126 个月前
2.1.02026/03/247 个月前
2.0.12026/03/207 个月前

关于 402-announce 的常见问题

402-announce 是什么?

在Nostr上公布HTTP 402服务(L402和x402),用于分权发现. Kind 31402参数化可替换事件.

如何安装 402-announce?

在项目目录运行 npm install 402-announce,或使用 pnpm add / yarn add / bun add。

402-announce 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

402-announce 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

402-announce 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

402-announce 可以商用吗?

402-announce 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

402-announce 的下载量有多少?

根据 npm 官方下载统计,402-announce 在 2026/10/04 – 2026/11/02 这 30 天内被下载 2,063 次,日均约 69 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

402-announce 还在维护吗?

最新版本 2.3.1 发布于 2026/10/18(20 天前);至今共发布 29 个版本,其中过去 12 个月发布了 29 个;首次发布于 2026/03/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。