跳至主要內容
入門

NIP-21 nostr: URI

Nostr URI 協議標準,定義 nostr: 連結格式,實現跨客戶端的連結互通。

8 分鐘

概述

NIP-21 定義了 nostr: URI 協議,讓 Nostr 實體可以在網頁、應用程式和 作業系統之間無縫連結。當用戶點擊 nostr: 連結時, 作業系統會自動開啟已註冊的 Nostr 客戶端來處理。

跨平台互通

就像 mailto: 連結開啟郵件客戶端,nostr: 連結 會開啟用戶偏好的 Nostr 應用程式,實現去中心化的連結體驗。

URI 格式

nostr: URI 由協議前綴加上 NIP-19 編碼組成:

{`nostr:`}

支援的編碼類型

前綴 說明 用途
npub 公鑰 連結到用戶資料
nprofile 公鑰 + 中繼器 更可靠的用戶連結
note 事件 ID 連結到貼文
nevent 事件 ID + 中繼資料 更可靠的事件連結
naddr 可替換事件地址 文章、列表等

安全注意

nsec(私鑰)不應該用於 nostr: URI。 私鑰絕對不應該出現在連結中。

URI 範例

用戶連結

{`# 簡單公鑰連結
nostr:npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6

# 帶中繼器資訊(推薦)
nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p`}

事件連結

{`# 簡單事件 ID 連結
nostr:note1fntxtkcy9pjwucqwa9mddn7v03wwwsu9j330jj350nvhpky2tuaspk6nqc

# 帶中繼器和作者資訊(推薦)
nostr:nevent1qqst8cujky046negxgwwm5ynqwn53t8aqjr6afd8g59nfqwxpdhylpcpzamhxue69uhhyetvv9ujuetd9ehx7um5wge5wqhh5...`}

可替換事件連結

{`# 長文章連結
nostr:naddr1qqxnzdesxqmnxvpexqunzvpcqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hj7qg4waehxw309ahx7um5wghx6at5d9h8jampd3kx2apwvdhk6...`}

實作

建立 nostr: URI

{`import { nip19 } from 'nostr-tools';

// 建立用戶連結
function createUserUri(pubkey: string, relays?: string[]): string {
  if (relays && relays.length > 0) {
    const nprofile = nip19.nprofileEncode({ pubkey, relays });
    return \`nostr:\${nprofile}\`;
  }
  const npub = nip19.npubEncode(pubkey);
  return \`nostr:\${npub}\`;
}

// 建立事件連結
function createEventUri(
  id: string,
  relays?: string[],
  author?: string
): string {
  if (relays || author) {
    const nevent = nip19.neventEncode({
      id,
      relays: relays || [],
      author,
    });
    return \`nostr:\${nevent}\`;
  }
  const note = nip19.noteEncode(id);
  return \`nostr:\${note}\`;
}

// 建立可替換事件連結
function createAddressUri(
  kind: number,
  pubkey: string,
  identifier: string,
  relays?: string[]
): string {
  const naddr = nip19.naddrEncode({
    kind,
    pubkey,
    identifier,
    relays: relays || [],
  });
  return \`nostr:\${naddr}\`;
}

// 使用範例
const userUri = createUserUri(
  '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
  ['wss://relay.damus.io', 'wss://nos.lol']
);
// nostr:nprofile1...

const eventUri = createEventUri(
  'e0b0f76c...',
  ['wss://relay.example.com'],
  '3bf0c63f...'
);
// nostr:nevent1...

const articleUri = createAddressUri(
  30023, // long-form content
  '3bf0c63f...',
  'my-article-slug',
  ['wss://relay.example.com']
);
// nostr:naddr1...`}

解析 nostr: URI

{`import { nip19 } from 'nostr-tools';

interface ParsedNostrUri {
  type: 'npub' | 'nprofile' | 'note' | 'nevent' | 'naddr';
  data: any;
}

function parseNostrUri(uri: string): ParsedNostrUri | null {
  // 驗證 URI 格式
  if (!uri.startsWith('nostr:')) {
    return null;
  }

  // 移除協議前綴
  const encoded = uri.slice(6);

  try {
    const decoded = nip19.decode(encoded);

    // 拒絕私鑰
    if (decoded.type === 'nsec') {
      throw new Error('nsec is not allowed in URIs');
    }

    return decoded as ParsedNostrUri;
  } catch (e) {
    console.error('Invalid nostr URI:', e);
    return null;
  }
}

// 處理不同類型
function handleNostrUri(uri: string) {
  const parsed = parseNostrUri(uri);
  if (!parsed) return;

  switch (parsed.type) {
    case 'npub':
      // 打開用戶資料頁面
      openProfile(parsed.data);
      break;

    case 'nprofile':
      // 使用指定的中繼器打開用戶資料
      openProfile(parsed.data.pubkey, parsed.data.relays);
      break;

    case 'note':
      // 打開事件/貼文
      openEvent(parsed.data);
      break;

    case 'nevent':
      // 使用指定的中繼器打開事件
      openEvent(parsed.data.id, parsed.data.relays, parsed.data.author);
      break;

    case 'naddr':
      // 打開可替換事件
      openAddress(
        parsed.data.kind,
        parsed.data.pubkey,
        parsed.data.identifier,
        parsed.data.relays
      );
      break;
  }
}`}

網頁整合

在 HTML 中使用 <link> 標籤關聯網頁內容與 Nostr 實體:

關聯作者

{`



`}

關聯內容

{`



`}

身份驗證

{`
`}

在網頁中建立可點擊的 Nostr 連結:

{`

  @fiatjaf




  查看這則貼文




  在 Nostr 上查看
`}

應用程式註冊

客戶端應用程式需要向作業系統註冊為 nostr: 協議處理器:

網頁應用

{`// 註冊為協議處理器(需要 HTTPS)
if ('registerProtocolHandler' in navigator) {
  navigator.registerProtocolHandler(
    'web+nostr',  // 注意:標準要求 web+ 前綴
    'https://your-client.com/open?uri=%s',
    'Your Nostr Client'
  );
}

// 處理傳入的 URI
const url = new URL(window.location.href);
const nostrUri = url.searchParams.get('uri');
if (nostrUri) {
  handleNostrUri(nostrUri);
}`}

桌面應用(macOS Info.plist)

{`CFBundleURLTypes

  
    CFBundleURLName
    Nostr Protocol
    CFBundleURLSchemes
    
      nostr
    
  
`}

Android(AndroidManifest.xml)

{`
  
  
  
  
`}

React 組件範例

{`import { nip19 } from 'nostr-tools';

interface NostrLinkProps {
  uri: string;
  children?: React.ReactNode;
  fallbackUrl?: string;
}

function NostrLink({ uri, children, fallbackUrl }: NostrLinkProps) {
  const parsed = parseNostrUri(uri);

  // 生成顯示文字
  const displayText = children || (() => {
    if (!parsed) return uri;

    switch (parsed.type) {
      case 'npub':
      case 'nprofile':
        const pubkey = parsed.type === 'npub'
          ? parsed.data
          : parsed.data.pubkey;
        return \`@\${pubkey.slice(0, 8)}...\`;

      case 'note':
      case 'nevent':
        const id = parsed.type === 'note'
          ? parsed.data
          : parsed.data.id;
        return \`note:\${id.slice(0, 8)}...\`;

      case 'naddr':
        return parsed.data.identifier || 'article';

      default:
        return uri;
    }
  })();

  const handleClick = (e: React.MouseEvent) => {
    // 嘗試開啟 nostr: URI
    window.location.href = uri;

    // 如果沒有處理器,fallback 到網頁版
    if (fallbackUrl) {
      setTimeout(() => {
        window.location.href = fallbackUrl;
      }, 500);
    }
  };

  return (
    
      {displayText}
    
  );
}

// 使用範例

  @fiatjaf
`}

網頁閘道器

對於沒有安裝 Nostr 客戶端的用戶,可以使用網頁閘道器來顯示內容:

{`// 將 nostr: URI 轉換為網頁閘道器連結
function toWebUrl(nostrUri: string): string {
  const encoded = nostrUri.replace('nostr:', '');
  return \`https://njump.me/\${encoded}\`;
}

// 範例
toWebUrl('nostr:npub1...')
// https://njump.me/npub1...`}

最佳實踐

  • 優先使用 nprofile/nevent:包含中繼器資訊,確保內容可發現
  • 提供 fallback:為沒有 Nostr 客戶端的用戶提供網頁版連結
  • 驗證輸入:解析 URI 時要處理錯誤情況
  • 拒絕 nsec:永遠不要在 URI 中包含私鑰
  • 顯示友善:將冗長的編碼轉換為可讀的顯示文字
  • NIP-01 - 基本協議與事件結構
  • NIP-19 - bech32 編碼(npub、note、nevent 等)
  • NIP-05 - 域名驗證(另一種身份標識方式)

參考資源

已複製連結
已複製到剪貼簿