diff --git a/.changeset/salty-news-taste.md b/.changeset/salty-news-taste.md
new file mode 100644
index 000000000..9f3475eef
--- /dev/null
+++ b/.changeset/salty-news-taste.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": minor
+---
+
+Linkify BIPs
diff --git a/src/components/content/components/bip.tsx b/src/components/content/components/bip.tsx
new file mode 100644
index 000000000..5eb865193
--- /dev/null
+++ b/src/components/content/components/bip.tsx
@@ -0,0 +1,12 @@
+import { Link, Tooltip } from "@chakra-ui/react";
+import { BIPToken } from "../transform/bip-notation";
+
+export default function BipDefinition({ node }: { node: BIPToken }) {
+ return (
+
+
+ {node.value}
+
+
+ );
+}
diff --git a/src/components/content/index.tsx b/src/components/content/index.tsx
index 8fa6e0f24..dd413e354 100644
--- a/src/components/content/index.tsx
+++ b/src/components/content/index.tsx
@@ -1,4 +1,3 @@
-import { lazy } from "react";
import { Link, Text } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { ComponentMap } from "applesauce-react/hooks";
@@ -6,9 +5,10 @@ import { ComponentMap } from "applesauce-react/hooks";
import Mention from "./components/mention";
import Cashu from "./components/cashu";
import { InlineEmoji } from "./components/ininle-emoji";
-import NipDefinition from "./components/nip";
import { ImageGallery } from "./components/gallery";
import LightningInvoice from "./components/lightning";
+import NipDefinition from "./components/nip";
+import BipDefinition from "./components/bip";
export const components: ComponentMap = {
text: ({ node }) => {node.value},
@@ -21,6 +21,7 @@ export const components: ComponentMap = {
),
nip: NipDefinition,
+ bip: BipDefinition,
gallery: ({ node }) => ,
lightning: ({ node }) => ,
};
diff --git a/src/components/content/transform/bip-notation.ts b/src/components/content/transform/bip-notation.ts
new file mode 100644
index 000000000..381665382
--- /dev/null
+++ b/src/components/content/transform/bip-notation.ts
@@ -0,0 +1,49 @@
+import { Transformer } from "unified";
+import { Root, findAndReplace, Node } from "applesauce-content/nast";
+import { BIP_NAMES } from "../../../const";
+
+export interface BIPToken extends Node {
+ type: "bip";
+ bip: number;
+ name: string;
+ value: string;
+}
+
+declare module "applesauce-content/nast" {
+ export interface BIPToken extends Node {
+ type: "bip";
+ bip: number;
+ name: string;
+ value: string;
+ }
+
+ export interface ContentMap {
+ bip: BIPToken;
+ }
+}
+
+export function bipDefinitions(): Transformer {
+ return (tree) => {
+ findAndReplace(tree, [
+ [
+ /(?<=^|[^\p{L}])bip[-\s]?(\d{1,3})/giu,
+ (match: string, $1: string) => {
+ try {
+ const bip = parseInt($1);
+ const name = BIP_NAMES[bip];
+ if (!name) return false;
+
+ return {
+ type: "bip",
+ bip,
+ value: match,
+ name,
+ };
+ } catch (error) {}
+
+ return false;
+ },
+ ],
+ ]);
+ };
+}
diff --git a/src/components/content/transform/nip-notation.ts b/src/components/content/transform/nip-notation.ts
index 6d6669f50..2e5f275ec 100644
--- a/src/components/content/transform/nip-notation.ts
+++ b/src/components/content/transform/nip-notation.ts
@@ -1,7 +1,6 @@
import { Transformer } from "unified";
import { Root, findAndReplace, Node } from "applesauce-content/nast";
-
-import { NIP_NAMES } from "../../../views/relays/components/supported-nips";
+import { NIP_NAMES } from "../../../const";
export interface NIPToken extends Node {
type: "nip";
diff --git a/src/components/media-post/media-post-content.tsx b/src/components/media-post/media-post-content.tsx
index dd3637b79..164f046e5 100644
--- a/src/components/media-post/media-post-content.tsx
+++ b/src/components/media-post/media-post-content.tsx
@@ -6,8 +6,9 @@ import { emojis, nostrMentions, links, hashtags } from "applesauce-content/text"
import { components } from "../content";
import { renderGenericUrl } from "../content/links";
import { nipDefinitions } from "../content/transform/nip-notation";
+import { bipDefinitions } from "../content/transform/bip-notation";
-const transformers = [links, nostrMentions, emojis, hashtags, nipDefinitions];
+const transformers = [links, nostrMentions, emojis, hashtags, nipDefinitions, bipDefinitions];
const linkRenderers = [renderGenericUrl];
diff --git a/src/components/note/timeline-note/text-note-contents.tsx b/src/components/note/timeline-note/text-note-contents.tsx
index ab529ad85..495917a43 100644
--- a/src/components/note/timeline-note/text-note-contents.tsx
+++ b/src/components/note/timeline-note/text-note-contents.tsx
@@ -29,8 +29,9 @@ import { LightboxProvider } from "../../lightbox-provider";
import MediaOwnerProvider from "../../../providers/local/media-owner-provider";
import { components } from "../../content";
import { nipDefinitions } from "../../content/transform/nip-notation";
+import { bipDefinitions } from "../../content/transform/bip-notation";
-const transformers = [...textNoteTransformers, galleries, nipDefinitions];
+const transformers = [...textNoteTransformers, galleries, nipDefinitions, bipDefinitions];
export type TextNoteContentsProps = {
event: NostrEvent | EventTemplate;
diff --git a/src/const.ts b/src/const.ts
index c858db2d9..d2321523a 100644
--- a/src/const.ts
+++ b/src/const.ts
@@ -87,3 +87,271 @@ export const NIP_89_CLIENT_APP: EventFactoryClient = {
export const SUPPORT_PUBKEY = "713978c3094081b34fcf2f5491733b0c22728cd3b7a6946519d40f5f08598af8";
export const TENOR_API_KEY = import.meta.env.VITE_TENOR_API_KEY as string | undefined;
+
+// copied from github
+export const NIP_NAMES: Record = {
+ "01": "Basic protocol",
+ "02": "Follow List",
+ "03": "OpenTimestamps Attestations for Events",
+ "04": "Encrypted Direct Message",
+ "05": "Mapping Nostr keys to DNS-based internet identifiers",
+ "06": "Basic key derivation from mnemonic seed phrase",
+ "07": "window.nostr capability for web browsers",
+ "08": "Handling Mentions",
+ "09": "Event Deletion Request",
+ "10": "Conventions for clients' use of `e` and `p` tags in text events",
+ "11": "Relay Information Document",
+ "13": "Proof of Work",
+ "14": "Subject tag in text events",
+ "15": "Nostr Marketplace (for resilient marketplaces)",
+ "17": "Private Direct Messages",
+ "18": "Reposts",
+ "19": "bech32-encoded entities",
+ "21": "nostr: URI scheme",
+ "22": "Comment",
+ "23": "Long-form Content",
+ "24": "Extra metadata fields and tags",
+ "25": "Reactions",
+ "26": "Delegated Event Signing",
+ "27": "Text Note References",
+ "28": "Public Chat",
+ "29": "Relay-based Groups",
+ "30": "Custom Emoji",
+ "31": "Dealing with Unknown Events",
+ "32": "Labeling",
+ "34": "git stuff",
+ "35": "Torrents",
+ "36": "Sensitive Content",
+ "37": "Draft Events",
+ "38": "User Statuses",
+ "39": "External Identities in Profiles",
+ "40": "Expiration Timestamp",
+ "42": "Authentication of clients to relays",
+ "44": "Encrypted Payloads (Versioned)",
+ "45": "Counting results",
+ "46": "Nostr Remote Signing",
+ "47": "Nostr Wallet Connect",
+ "48": "Proxy Tags",
+ "49": "Private Key Encryption",
+ "50": "Search Capability",
+ "51": "Lists",
+ "52": "Calendar Events",
+ "53": "Live Activities",
+ "54": "Wiki",
+ "55": "Android Signer Application",
+ "56": "Reporting",
+ "57": "Lightning Zaps",
+ "58": "Badges",
+ "59": "Gift Wrap",
+ "60": "Cashu Wallet",
+ "61": "Nutzaps",
+ "64": "Chess (PGN)",
+ "65": "Relay List Metadata",
+ "68": "Picture-first feeds",
+ "69": "Peer-to-peer Order events",
+ "70": "Protected Events",
+ "71": "Video Events",
+ "72": "Moderated Communities",
+ "73": "External Content IDs",
+ "75": "Zap Goals",
+ "78": "Application-specific data",
+ "84": "Highlights",
+ "86": "Relay Management API",
+ "89": "Recommended Application Handlers",
+ "90": "Data Vending Machines",
+ "92": "Media Attachments",
+ "94": "File Metadata",
+ "96": "HTTP File Storage Integration",
+ "98": "HTTP Auth",
+ "99": "Classified Listings",
+ "7D": "Threads",
+ C7: "Chats",
+};
+
+// Copied from https://github.com/bitcoin/bips on 02/28/2025
+export const BIP_NAMES: Record = {
+ "1": "BIP Purpose and Guidelines",
+ "2": "BIP process, revised",
+ "3": "Updated BIP Process",
+ "8": "Version bits with lock-in by height",
+ "9": "Version bits with timeout and delay",
+ "10": "Multi-Sig Transaction Distribution",
+ "11": "M-of-N Standard Transactions",
+ "12": "OP_EVAL",
+ "13": "Address Format for pay-to-script-hash",
+ "14": "Protocol Version and User Agent",
+ "15": "Aliases",
+ "16": "Pay to Script Hash",
+ "17": "OP_CHECKHASHVERIFY (CHV)",
+ "18": "hashScriptCheck",
+ "19": "M-of-N Standard Transactions (Low SigOp)",
+ "20": "URI Scheme",
+ "21": "URI Scheme",
+ "22": "getblocktemplate - Fundamentals",
+ "23": "getblocktemplate - Pooled Mining",
+ "30": "Duplicate transactions",
+ "31": "Pong message",
+ "32": "Hierarchical Deterministic Wallets",
+ "33": "Stratized Nodes",
+ "34": "Block v2, Height in Coinbase",
+ "35": "mempool message",
+ "36": "Custom Services",
+ "37": "Connection Bloom filtering",
+ "38": "Passphrase-protected private key",
+ "39": "Mnemonic code for generating deterministic keys",
+ "40": "Stratum wire protocol",
+ "41": "Stratum mining protocol",
+ "42": "A finite monetary supply for Bitcoin",
+ "43": "Purpose Field for Deterministic Wallets",
+ "44": "Multi-Account Hierarchy for Deterministic Wallets",
+ "45": "Structure for Deterministic P2SH Multisignature Wallets",
+ "46": "Address Scheme for Timelocked Fidelity Bonds",
+ "47": "Reusable Payment Codes for Hierarchical Deterministic Wallets",
+ "48": "Multi-Script Hierarchy for Multi-Sig Wallets",
+ "49": "Derivation scheme for P2WPKH-nested-in-P2SH based accounts",
+ "50": "March 2013 Chain Fork Post-Mortem",
+ "52": "Durable, Low Energy Bitcoin PoW",
+ "60": 'Fixed Length "version" Message (Relay-Transactions Field)',
+ "61": "Reject P2P message",
+ "62": "Dealing with malleability",
+ "63": "Stealth Addresses",
+ "64": "getutxo message",
+ "65": "OP_CHECKLOCKTIMEVERIFY",
+ "66": "Strict DER signatures",
+ "67": "Deterministic Pay-to-script-hash multi-signature addresses through public key sorting",
+ "68": "Relative lock-time using consensus-enforced sequence numbers",
+ "69": "Lexicographical Indexing of Transaction Inputs and Outputs",
+ "70": "Payment Protocol",
+ "71": "Payment Protocol MIME types",
+ "72": "bitcoin: uri extensions for Payment Protocol",
+ "73": 'Use "Accept" header for response type negotiation with Payment Request URLs',
+ "74": "Allow zero value OP_RETURN in Payment Protocol",
+ "75": "Out of Band Address Exchange using Payment Protocol Encryption",
+ "78": "A Simple Payjoin Proposal",
+ "79": "Bustapay :: a practical coinjoin protocol",
+ "80": "Hierarchy for Non-Colored Voting Pool Deterministic Multisig Wallets",
+ "81": "Hierarchy for Colored Voting Pool Deterministic Multisig Wallets",
+ "83": "Dynamic Hierarchical Deterministic Key Trees",
+ "84": "Derivation scheme for P2WPKH based accounts",
+ "85": "Deterministic Entropy From BIP32 Keychains",
+ "86": "Key Derivation for Single Key P2TR Outputs",
+ "87": "Hierarchy for Deterministic Multisig Wallets",
+ "88": "Hierarchical Deterministic Path Templates",
+ "90": "Buried Deployments",
+ "91": "Reduced threshold Segwit MASF",
+ "93": "codex32: Checksummed SSSS-aware BIP32 seeds",
+ "94": "Testnet 4",
+ "98": "Fast Merkle Trees",
+ "99": "Motivation and deployment of consensus rule changes ([soft/hard]forks)",
+ "100": "Dynamic maximum block size by miner vote",
+ "101": "Increase maximum block size",
+ "102": "Block size increase to 2MB",
+ "103": "Block size following technological growth",
+ "104": "'Block75' - Max block size like difficulty",
+ "105": "Consensus based block size retargeting algorithm",
+ "106": "Dynamically Controlled Bitcoin Block Size Max Cap",
+ "107": "Dynamic limit on the block size",
+ "109": "Two million byte size limit with sigop and sighash limits",
+ "111": "NODE_BLOOM service bit",
+ "112": "CHECKSEQUENCEVERIFY",
+ "113": "Median time-past as endpoint for lock-time calculations",
+ "114": "Merkelized Abstract Syntax Tree",
+ "115": "Generic anti-replay protection using Script",
+ "116": "MERKLEBRANCHVERIFY",
+ "117": "Tail Call Execution Semantics",
+ "118": "SIGHASH_ANYPREVOUT for Taproot Scripts",
+ "119": "CHECKTEMPLATEVERIFY",
+ "120": "Proof of Payment",
+ "121": "Proof of Payment URI scheme",
+ "122": "URI scheme for Blockchain references / exploration",
+ "123": "BIP Classification",
+ "124": "Hierarchical Deterministic Script Templates",
+ "125": "Opt-in Full Replace-by-Fee Signaling",
+ "126": "Best Practices for Heterogeneous Input Script Transactions",
+ "127": "Simple Proof-of-Reserves Transactions",
+ "129": "Bitcoin Secure Multisig Setup (BSMS)",
+ "130": "sendheaders message",
+ "131": '"Coalescing Transaction" Specification (wildcard inputs)',
+ "132": "Committee-based BIP Acceptance Process",
+ "133": "feefilter message",
+ "134": "Flexible Transactions",
+ "135": "Generalized version bits voting",
+ "136": "Bech32 Encoded Tx Position References",
+ "137": "Signatures of Messages using Private Keys",
+ "140": "Normalized TXID",
+ "141": "Segregated Witness (Consensus layer)",
+ "142": "Address Format for Segregated Witness",
+ "143": "Transaction Signature Verification for Version 0 Witness Program",
+ "144": "Segregated Witness (Peer Services)",
+ "145": "getblocktemplate Updates for Segregated Witness",
+ "146": "Dealing with signature encoding malleability",
+ "147": "Dealing with dummy stack element malleability",
+ "148": "Mandatory activation of segwit deployment",
+ "149": "Segregated Witness (second deployment)",
+ "150": "Peer Authentication",
+ "151": "Peer-to-Peer Communication Encryption",
+ "152": "Compact Block Relay",
+ "154": "Rate Limiting via peer specified challenges",
+ "155": "addrv2 message",
+ "156": "Dandelion - Privacy Enhancing Routing",
+ "157": "Client Side Block Filtering",
+ "158": "Compact Block Filters for Light Clients",
+ "159": "NODE_NETWORK_LIMITED service bit",
+ "171": "Currency/exchange rate information API",
+ "173": "Base32 address format for native v0-16 witness outputs",
+ "174": "Partially Signed Bitcoin Transaction Format",
+ "175": "Pay to Contract Protocol",
+ "176": "Bits Denomination",
+ "178": "Version Extended WIF",
+ "179": "Name for payment recipient identifiers",
+ "180": "Block size/weight fraud proof",
+ "197": "Hashed Time-Locked Collateral Contract",
+ "199": "Hashed Time-Locked Contract transactions",
+ "300": "Hashrate Escrows (Consensus layer)",
+ "301": "Blind Merged Mining (Consensus layer)",
+ "310": "Stratum protocol extensions",
+ "320": "nVersion bits for general purpose use",
+ "322": "Generic Signed Message Format",
+ "324": "Version 2 P2P Encrypted Transport Protocol",
+ "325": "Signet",
+ "326": "Anti-fee-sniping in taproot transactions",
+ "327": "MuSig2 for BIP340-compatible Multi-Signatures",
+ "328": "Derivation Scheme for MuSig2 Aggregate Keys",
+ "329": "Wallet Labels Export Format",
+ "330": "Transaction announcements reconciliation",
+ "331": "Ancestor Package Relay",
+ "337": "Compressed Transactions",
+ "338": "Disable transaction relay message",
+ "339": "WTXID-based transaction relay",
+ "340": "Schnorr Signatures for secp256k1",
+ "341": "Taproot: SegWit version 1 spending rules",
+ "342": "Validation of Taproot Scripts",
+ "343": "Mandatory activation of taproot deployment",
+ "345": "OP_VAULT",
+ "347": "OP_CAT in Tapscript",
+ "348": "CHECKSIGFROMSTACK",
+ "349": "OP_INTERNALKEY",
+ "350": "Bech32m format for v1+ witness addresses",
+ "351": "Private Payments",
+ "352": "Silent Payments",
+ "353": "DNS Payment Instructions",
+ "370": "PSBT Version 2",
+ "371": "Taproot Fields for PSBT",
+ "372": "Pay-to-contract tweak fields for PSBT",
+ "373": "MuSig2 PSBT Fields",
+ "374": "Discrete Log Equality Proofs",
+ "375": "Sending Silent Payments with PSBTs",
+ "379": "Miniscript",
+ "380": "Output Script Descriptors General Operation",
+ "381": "Non-Segwit Output Script Descriptors",
+ "382": "Segwit Output Script Descriptors",
+ "383": "Multisig Output Script Descriptors",
+ "384": "combo() Output Script Descriptors",
+ "385": "raw() and addr() Output Script Descriptors",
+ "386": "tr() Output Script Descriptors",
+ "387": "Tapscript Multisig Output Script Descriptors",
+ "388": "Wallet Policies for Descriptor Wallets",
+ "389": "Multipath Descriptor Key Expressions",
+ "390": "musig() Descriptor Key Expression",
+ "431": "Topology Restrictions for Pinning",
+};
diff --git a/src/views/relays/components/supported-nips.tsx b/src/views/relays/components/supported-nips.tsx
index cd887b224..12a86433a 100644
--- a/src/views/relays/components/supported-nips.tsx
+++ b/src/views/relays/components/supported-nips.tsx
@@ -1,84 +1,5 @@
import { Flex, FlexProps, Tag, Tooltip } from "@chakra-ui/react";
-
-// copied from github
-export const NIP_NAMES: Record = {
- "01": "Basic protocol",
- "02": "Follow List",
- "03": "OpenTimestamps Attestations for Events",
- "04": "Encrypted Direct Message",
- "05": "Mapping Nostr keys to DNS-based internet identifiers",
- "06": "Basic key derivation from mnemonic seed phrase",
- "07": "window.nostr capability for web browsers",
- "08": "Handling Mentions",
- "09": "Event Deletion Request",
- "10": "Conventions for clients' use of `e` and `p` tags in text events",
- "11": "Relay Information Document",
- "13": "Proof of Work",
- "14": "Subject tag in text events",
- "15": "Nostr Marketplace (for resilient marketplaces)",
- "17": "Private Direct Messages",
- "18": "Reposts",
- "19": "bech32-encoded entities",
- "21": "nostr: URI scheme",
- "22": "Comment",
- "23": "Long-form Content",
- "24": "Extra metadata fields and tags",
- "25": "Reactions",
- "26": "Delegated Event Signing",
- "27": "Text Note References",
- "28": "Public Chat",
- "29": "Relay-based Groups",
- "30": "Custom Emoji",
- "31": "Dealing with Unknown Events",
- "32": "Labeling",
- "34": "git stuff",
- "35": "Torrents",
- "36": "Sensitive Content",
- "37": "Draft Events",
- "38": "User Statuses",
- "39": "External Identities in Profiles",
- "40": "Expiration Timestamp",
- "42": "Authentication of clients to relays",
- "44": "Encrypted Payloads (Versioned)",
- "45": "Counting results",
- "46": "Nostr Remote Signing",
- "47": "Nostr Wallet Connect",
- "48": "Proxy Tags",
- "49": "Private Key Encryption",
- "50": "Search Capability",
- "51": "Lists",
- "52": "Calendar Events",
- "53": "Live Activities",
- "54": "Wiki",
- "55": "Android Signer Application",
- "56": "Reporting",
- "57": "Lightning Zaps",
- "58": "Badges",
- "59": "Gift Wrap",
- "60": "Cashu Wallet",
- "61": "Nutzaps",
- "64": "Chess (PGN)",
- "65": "Relay List Metadata",
- "68": "Picture-first feeds",
- "69": "Peer-to-peer Order events",
- "70": "Protected Events",
- "71": "Video Events",
- "72": "Moderated Communities",
- "73": "External Content IDs",
- "75": "Zap Goals",
- "78": "Application-specific data",
- "84": "Highlights",
- "86": "Relay Management API",
- "89": "Recommended Application Handlers",
- "90": "Data Vending Machines",
- "92": "Media Attachments",
- "94": "File Metadata",
- "96": "HTTP File Storage Integration",
- "98": "HTTP Auth",
- "99": "Classified Listings",
- "7D": "Threads",
- C7: "Chats",
-};
+import { NIP_NAMES } from "../../../const";
function NipTag({ nip, name }: { nip: number; name?: boolean }) {
const nipStr = String(nip).padStart(2, "0");
diff --git a/src/views/streams/stream/stream-chat/chat-message-content.tsx b/src/views/streams/stream/stream-chat/chat-message-content.tsx
index aeba9876f..d875fdadd 100644
--- a/src/views/streams/stream/stream-chat/chat-message-content.tsx
+++ b/src/views/streams/stream/stream-chat/chat-message-content.tsx
@@ -12,9 +12,10 @@ import { NostrEvent } from "../../../../types/nostr-event";
import { components } from "../../../../components/content";
import { textNoteTransformers } from "applesauce-content/text";
import { nipDefinitions } from "../../../../components/content/transform/nip-notation";
+import { bipDefinitions } from "../../../../components/content/transform/bip-notation";
const StreamChatMessageContentSymbol = Symbol.for("stream-chat-message-content");
-const transformers = [...textNoteTransformers, nipDefinitions];
+const transformers = [...textNoteTransformers, nipDefinitions, bipDefinitions];
const linkRenderers = [renderImageUrl, renderWavlakeUrl, renderStemstrUrl, renderSoundCloudUrl, renderGenericUrl];
const ChatMessageContent = React.memo(({ event }: { event: NostrEvent }) => {