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 }) => {