--- /dev/null
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from "../constants"
+import { base32, bytes, hex } from "../convert"
+import { Blake2b } from "../crypto"
+
+class Address {
+ #address: string
+
+ /**
+ * Converts a public key to a Nano address.
+ *
+ * @param {Uint8Array} publicKey - Public key bytes as Uint8Array
+ * @returns Nano address string using `nano_` prefix
+ */
+ static fromPublicKey (publicKey: ArrayBuffer | Uint8Array<ArrayBuffer>): string {
+ const checksum = new Blake2b(5).update(publicKey).digest().reverse()
+ const encodedPublicKey = bytes.toBase32(publicKey)
+ const encodedChecksum = bytes.toBase32(checksum)
+ return `${PREFIX}${encodedPublicKey}${encodedChecksum}`
+ }
+
+ constructor (address: string)
+ constructor (publicKey: string | ArrayBuffer | Uint8Array<ArrayBuffer>)
+ constructor (value: unknown) {
+ if (typeof value !== 'string' && !(value instanceof ArrayBuffer) && !(value instanceof Uint8Array)) {
+ throw new TypeError('Invalid address input', { cause: value })
+ }
+ if (typeof value === 'string') {
+ if (RegExp(`^[A-F0-9]{${ACCOUNT_KEY_HEX_LENGTH}}$`, 'i').test(value)) {
+ this.#address = Address.fromPublicKey(hex.toBuffer(value))
+ } else if (RegExp(`^(${PREFIX}|${PREFIX_LEGACY})[13][${ALPHABET}]{59}$`).test(value)) {
+ this.#address = value
+ } else {
+ throw new TypeError('Invalid address input', { cause: value })
+ }
+ } else if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
+ const v = new Uint8Array(value)
+ this.#address = Address.fromPublicKey(v)
+ } else {
+ throw new TypeError('Invalid address input', { cause: value })
+ }
+ this.#address = this.#address.replace(PREFIX_LEGACY, PREFIX)
+ }
+
+ [Symbol.toPrimitive] (hint: string) {
+ if (hint === 'string' || hint === 'default') {
+ return this.#address
+ }
+ throw new SyntaxError('invalid Address syntax')
+ }
+
+ get length (): number { return `${PREFIX}${this.#address}`.length }
+
+ toString (): string { return `${PREFIX}${this.#address}` }
+
+ valueOf (): string { return `${PREFIX}${this.#address}` }
+
+ /**
+ * Converts a Nano address to a public key.
+ *
+ * @returns Public key bytes as ArrayBuffer
+ */
+ toPublicKey (): ArrayBuffer {
+ const publicKey = base32.toBuffer(this.#address.slice(-60, -8))
+ const checksum = base32.toBuffer(this.#address.slice(-8))
+ const rechecksum = new Blake2b(5).update(publicKey).digest().reverse()
+ if (bytes.toHex(checksum) !== bytes.toHex(rechecksum)) {
+ throw new Error('Checksum mismatch in address')
+ }
+ return publicKey
+ }
+}