Pubkey.ts raw

   1  import { nip19 } from 'nostr-tools'
   2  import { InvalidPubkeyError } from '../errors'
   3  
   4  /**
   5   * Value object representing a Nostr public key.
   6   * Immutable, self-validating, with equality by value.
   7   */
   8  export class Pubkey {
   9    private constructor(private readonly _value: string) {}
  10  
  11    /**
  12     * Create a Pubkey from a 64-character hex string.
  13     * @throws InvalidPubkeyError if the hex is invalid
  14     */
  15    static fromHex(hex: string): Pubkey {
  16      if (!/^[0-9a-f]{64}$/.test(hex)) {
  17        throw new InvalidPubkeyError(hex)
  18      }
  19      return new Pubkey(hex)
  20    }
  21  
  22    /**
  23     * Create a Pubkey from an npub bech32 string.
  24     * @throws InvalidPubkeyError if the npub is invalid
  25     */
  26    static fromNpub(npub: string): Pubkey {
  27      try {
  28        const { type, data } = nip19.decode(npub)
  29        if (type !== 'npub') {
  30          throw new InvalidPubkeyError(npub)
  31        }
  32        return new Pubkey(data)
  33      } catch (e) {
  34        if (e instanceof InvalidPubkeyError) throw e
  35        throw new InvalidPubkeyError(npub)
  36      }
  37    }
  38  
  39    /**
  40     * Create a Pubkey from an nprofile bech32 string.
  41     * @throws InvalidPubkeyError if the nprofile is invalid
  42     */
  43    static fromNprofile(nprofile: string): Pubkey {
  44      try {
  45        const { type, data } = nip19.decode(nprofile)
  46        if (type !== 'nprofile') {
  47          throw new InvalidPubkeyError(nprofile)
  48        }
  49        return new Pubkey(data.pubkey)
  50      } catch (e) {
  51        if (e instanceof InvalidPubkeyError) throw e
  52        throw new InvalidPubkeyError(nprofile)
  53      }
  54    }
  55  
  56    /**
  57     * Try to create a Pubkey from any string format (hex, npub, nprofile).
  58     * Returns null if the string is invalid.
  59     */
  60    static tryFromString(input: string): Pubkey | null {
  61      try {
  62        if (input.startsWith('npub1')) {
  63          return Pubkey.fromNpub(input)
  64        }
  65        if (input.startsWith('nprofile1')) {
  66          return Pubkey.fromNprofile(input)
  67        }
  68        return Pubkey.fromHex(input)
  69      } catch {
  70        return null
  71      }
  72    }
  73  
  74    /**
  75     * Check if a string is a valid pubkey (hex format only).
  76     */
  77    static isValidHex(value: string): boolean {
  78      return /^[0-9a-f]{64}$/.test(value)
  79    }
  80  
  81    /** The raw 64-character hex value */
  82    get hex(): string {
  83      return this._value
  84    }
  85  
  86    /** The bech32-encoded npub representation */
  87    get npub(): string {
  88      return nip19.npubEncode(this._value)
  89    }
  90  
  91    /** A shortened display format: first 8 + ... + last 4 characters */
  92    get formatted(): string {
  93      return `${this._value.slice(0, 8)}...${this._value.slice(-4)}`
  94    }
  95  
  96    /** A shorter display format for the npub */
  97    formatNpub(length = 12): string {
  98      const npub = this.npub
  99      if (length < 12) length = 12
 100      if (length >= 63) return npub
 101  
 102      const prefixLength = Math.floor((length - 5) / 2) + 5
 103      const suffixLength = length - prefixLength
 104      return npub.slice(0, prefixLength) + '...' + npub.slice(-suffixLength)
 105    }
 106  
 107    /** Check equality with another Pubkey */
 108    equals(other: Pubkey): boolean {
 109      return this._value === other._value
 110    }
 111  
 112    /** Returns the hex representation */
 113    toString(): string {
 114      return this._value
 115    }
 116  
 117    /** For JSON serialization */
 118    toJSON(): string {
 119      return this._value
 120    }
 121  }
 122