@unicitylabs/sphere-sdk 0.5.7 → 0.6.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../impl/browser/ipfs.ts","../../../impl/shared/ipfs/ipfs-error-types.ts","../../../impl/shared/ipfs/ipfs-state-persistence.ts","../../../node_modules/@noble/hashes/src/utils.ts","../../../node_modules/@noble/hashes/src/hmac.ts","../../../node_modules/@noble/hashes/src/hkdf.ts","../../../node_modules/@noble/hashes/src/_md.ts","../../../node_modules/@noble/hashes/src/sha2.ts","../../../core/crypto.ts","../../../core/errors.ts","../../../impl/shared/ipfs/ipns-key-derivation.ts","../../../impl/shared/ipfs/ipns-record-manager.ts","../../../impl/shared/ipfs/ipfs-cache.ts","../../../core/logger.ts","../../../impl/shared/ipfs/ipfs-http-client.ts","../../../impl/shared/ipfs/txf-merge.ts","../../../transport/websocket.ts","../../../impl/shared/ipfs/ipns-subscription-client.ts","../../../impl/shared/ipfs/write-behind-buffer.ts","../../../constants.ts","../../../impl/shared/ipfs/ipfs-storage-provider.ts","../../../impl/browser/ipfs/browser-ipfs-state-persistence.ts","../../../impl/browser/ipfs/index.ts"],"sourcesContent":["/**\n * Browser IPFS Storage Provider\n *\n * Separate entry point for IPFS functionality.\n *\n * @example\n * ```ts\n * import { IpfsStorageProvider, createIpfsStorageProvider } from '@unicitylabs/sphere-sdk/impl/browser/ipfs';\n * ```\n */\nexport {\n IpfsStorageProvider,\n createBrowserIpfsStorageProvider,\n createIpfsStorageProvider,\n BrowserIpfsStatePersistence,\n type IpfsStorageProviderConfig,\n} from './ipfs/index';\n","/**\n * IPFS Error Classification\n * Categorizes errors for proper handling (e.g., NOT_FOUND should not trigger circuit breaker)\n */\n\n// =============================================================================\n// Error Categories\n// =============================================================================\n\nexport type IpfsErrorCategory =\n | 'NOT_FOUND' // IPNS record never published (expected for new wallets)\n | 'NETWORK_ERROR' // Connectivity / server issues\n | 'TIMEOUT' // Request timed out\n | 'GATEWAY_ERROR' // Gateway returned error (5xx, etc.)\n | 'INVALID_RESPONSE' // Response parsing failed\n | 'CID_MISMATCH' // Content hash doesn't match CID\n | 'SEQUENCE_DOWNGRADE'; // Remote sequence < local (stale data)\n\n// =============================================================================\n// Error Class\n// =============================================================================\n\nexport class IpfsError extends Error {\n readonly category: IpfsErrorCategory;\n readonly gateway?: string;\n readonly cause?: Error;\n\n constructor(\n message: string,\n category: IpfsErrorCategory,\n gateway?: string,\n cause?: Error,\n ) {\n super(message);\n this.name = 'IpfsError';\n this.category = category;\n this.gateway = gateway;\n this.cause = cause;\n }\n\n /** Whether this error should trigger the circuit breaker */\n get shouldTriggerCircuitBreaker(): boolean {\n // NOT_FOUND is expected for new wallets, don't penalize the gateway\n return this.category !== 'NOT_FOUND' && this.category !== 'SEQUENCE_DOWNGRADE';\n }\n}\n\n// =============================================================================\n// Error Classification Helpers\n// =============================================================================\n\n/**\n * Classify a fetch exception into an IpfsErrorCategory\n */\nexport function classifyFetchError(error: unknown): IpfsErrorCategory {\n if (error instanceof DOMException && error.name === 'AbortError') {\n return 'TIMEOUT';\n }\n if (error instanceof TypeError) {\n // TypeError typically means network failure (DNS, connection refused, etc.)\n return 'NETWORK_ERROR';\n }\n if (error instanceof Error && error.name === 'TimeoutError') {\n return 'TIMEOUT';\n }\n return 'NETWORK_ERROR';\n}\n\n/**\n * Classify an HTTP status code into an IpfsErrorCategory\n * @param status - HTTP status code\n * @param responseBody - Optional response body for additional context\n */\nexport function classifyHttpStatus(\n status: number,\n responseBody?: string,\n): IpfsErrorCategory {\n if (status === 404) {\n return 'NOT_FOUND';\n }\n\n if (status === 500 && responseBody) {\n // Kubo returns 500 with \"routing: not found\" for IPNS records that don't exist\n if (/routing:\\s*not\\s*found/i.test(responseBody)) {\n return 'NOT_FOUND';\n }\n }\n\n if (status >= 500) {\n return 'GATEWAY_ERROR';\n }\n\n if (status >= 400) {\n return 'GATEWAY_ERROR';\n }\n\n return 'GATEWAY_ERROR';\n}\n","/**\n * IPFS State Persistence\n * Interface and in-memory implementation for persisting IPFS/IPNS state\n */\n\nimport type { IpfsStatePersistence, IpfsPersistedState } from './ipfs-types';\n\n// Re-export for convenience\nexport type { IpfsStatePersistence, IpfsPersistedState };\n\n// =============================================================================\n// In-Memory Implementation (for testing)\n// =============================================================================\n\nexport class InMemoryIpfsStatePersistence implements IpfsStatePersistence {\n private readonly states = new Map<string, IpfsPersistedState>();\n\n async load(ipnsName: string): Promise<IpfsPersistedState | null> {\n return this.states.get(ipnsName) ?? null;\n }\n\n async save(ipnsName: string, state: IpfsPersistedState): Promise<void> {\n this.states.set(ipnsName, { ...state });\n }\n\n async clear(ipnsName: string): Promise<void> {\n this.states.delete(ipnsName);\n }\n}\n","/**\n * Utilities for hex, bytes, CSPRNG.\n * @module\n */\n/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */\n/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */\nexport function isBytes(a: unknown): a is Uint8Array {\n return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');\n}\n\n/** Asserts something is positive integer. */\nexport function anumber(n: number, title: string = ''): void {\n if (!Number.isSafeInteger(n) || n < 0) {\n const prefix = title && `\"${title}\" `;\n throw new Error(`${prefix}expected integer >= 0, got ${n}`);\n }\n}\n\n/** Asserts something is Uint8Array. */\nexport function abytes(value: Uint8Array, length?: number, title: string = ''): Uint8Array {\n const bytes = isBytes(value);\n const len = value?.length;\n const needsLen = length !== undefined;\n if (!bytes || (needsLen && len !== length)) {\n const prefix = title && `\"${title}\" `;\n const ofLen = needsLen ? ` of length ${length}` : '';\n const got = bytes ? `length=${len}` : `type=${typeof value}`;\n throw new Error(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);\n }\n return value;\n}\n\n/** Asserts something is hash */\nexport function ahash(h: CHash): void {\n if (typeof h !== 'function' || typeof h.create !== 'function')\n throw new Error('Hash must wrapped by utils.createHasher');\n anumber(h.outputLen);\n anumber(h.blockLen);\n}\n\n/** Asserts a hash instance has not been destroyed / finished */\nexport function aexists(instance: any, checkFinished = true): void {\n if (instance.destroyed) throw new Error('Hash instance has been destroyed');\n if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called');\n}\n\n/** Asserts output is properly-sized byte array */\nexport function aoutput(out: any, instance: any): void {\n abytes(out, undefined, 'digestInto() output');\n const min = instance.outputLen;\n if (out.length < min) {\n throw new Error('\"digestInto() output\" expected to be of length >=' + min);\n }\n}\n\n/** Generic type encompassing 8/16/32-byte arrays - but not 64-byte. */\n// prettier-ignore\nexport type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array |\n Uint16Array | Int16Array | Uint32Array | Int32Array;\n\n/** Cast u8 / u16 / u32 to u8. */\nexport function u8(arr: TypedArray): Uint8Array {\n return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/** Cast u8 / u16 / u32 to u32. */\nexport function u32(arr: TypedArray): Uint32Array {\n return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));\n}\n\n/** Zeroize a byte array. Warning: JS provides no guarantees. */\nexport function clean(...arrays: TypedArray[]): void {\n for (let i = 0; i < arrays.length; i++) {\n arrays[i].fill(0);\n }\n}\n\n/** Create DataView of an array for easy byte-level manipulation. */\nexport function createView(arr: TypedArray): DataView {\n return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/** The rotate right (circular right shift) operation for uint32 */\nexport function rotr(word: number, shift: number): number {\n return (word << (32 - shift)) | (word >>> shift);\n}\n\n/** The rotate left (circular left shift) operation for uint32 */\nexport function rotl(word: number, shift: number): number {\n return (word << shift) | ((word >>> (32 - shift)) >>> 0);\n}\n\n/** Is current platform little-endian? Most are. Big-Endian platform: IBM */\nexport const isLE: boolean = /* @__PURE__ */ (() =>\n new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();\n\n/** The byte swap operation for uint32 */\nexport function byteSwap(word: number): number {\n return (\n ((word << 24) & 0xff000000) |\n ((word << 8) & 0xff0000) |\n ((word >>> 8) & 0xff00) |\n ((word >>> 24) & 0xff)\n );\n}\n/** Conditionally byte swap if on a big-endian platform */\nexport const swap8IfBE: (n: number) => number = isLE\n ? (n: number) => n\n : (n: number) => byteSwap(n);\n\n/** In place byte swap for Uint32Array */\nexport function byteSwap32(arr: Uint32Array): Uint32Array {\n for (let i = 0; i < arr.length; i++) {\n arr[i] = byteSwap(arr[i]);\n }\n return arr;\n}\n\nexport const swap32IfBE: (u: Uint32Array) => Uint32Array = isLE\n ? (u: Uint32Array) => u\n : byteSwap32;\n\n// Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex\nconst hasHexBuiltin: boolean = /* @__PURE__ */ (() =>\n // @ts-ignore\n typeof Uint8Array.from([]).toHex === 'function' && typeof Uint8Array.fromHex === 'function')();\n\n// Array where index 0xf0 (240) is mapped to string 'f0'\nconst hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>\n i.toString(16).padStart(2, '0')\n);\n\n/**\n * Convert byte array to hex string. Uses built-in function, when available.\n * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'\n */\nexport function bytesToHex(bytes: Uint8Array): string {\n abytes(bytes);\n // @ts-ignore\n if (hasHexBuiltin) return bytes.toHex();\n // pre-caching improves the speed 6x\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += hexes[bytes[i]];\n }\n return hex;\n}\n\n// We use optimized technique to convert hex string to byte array\nconst asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const;\nfunction asciiToBase16(ch: number): number | undefined {\n if (ch >= asciis._0 && ch <= asciis._9) return ch - asciis._0; // '2' => 50-48\n if (ch >= asciis.A && ch <= asciis.F) return ch - (asciis.A - 10); // 'B' => 66-(65-10)\n if (ch >= asciis.a && ch <= asciis.f) return ch - (asciis.a - 10); // 'b' => 98-(97-10)\n return;\n}\n\n/**\n * Convert hex string to byte array. Uses built-in function, when available.\n * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])\n */\nexport function hexToBytes(hex: string): Uint8Array {\n if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);\n // @ts-ignore\n if (hasHexBuiltin) return Uint8Array.fromHex(hex);\n const hl = hex.length;\n const al = hl / 2;\n if (hl % 2) throw new Error('hex string expected, got unpadded hex of length ' + hl);\n const array = new Uint8Array(al);\n for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {\n const n1 = asciiToBase16(hex.charCodeAt(hi));\n const n2 = asciiToBase16(hex.charCodeAt(hi + 1));\n if (n1 === undefined || n2 === undefined) {\n const char = hex[hi] + hex[hi + 1];\n throw new Error('hex string expected, got non-hex character \"' + char + '\" at index ' + hi);\n }\n array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163\n }\n return array;\n}\n\n/**\n * There is no setImmediate in browser and setTimeout is slow.\n * Call of async fn will return Promise, which will be fullfiled only on\n * next scheduler queue processing step and this is exactly what we need.\n */\nexport const nextTick = async (): Promise<void> => {};\n\n/** Returns control to thread each 'tick' ms to avoid blocking. */\nexport async function asyncLoop(\n iters: number,\n tick: number,\n cb: (i: number) => void\n): Promise<void> {\n let ts = Date.now();\n for (let i = 0; i < iters; i++) {\n cb(i);\n // Date.now() is not monotonic, so in case if clock goes backwards we return return control too\n const diff = Date.now() - ts;\n if (diff >= 0 && diff < tick) continue;\n await nextTick();\n ts += diff;\n }\n}\n\n// Global symbols, but ts doesn't see them: https://github.com/microsoft/TypeScript/issues/31535\ndeclare const TextEncoder: any;\n\n/**\n * Converts string to bytes using UTF8 encoding.\n * Built-in doesn't validate input to be string: we do the check.\n * @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])\n */\nexport function utf8ToBytes(str: string): Uint8Array {\n if (typeof str !== 'string') throw new Error('string expected');\n return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809\n}\n\n/** KDFs can accept string or Uint8Array for user convenience. */\nexport type KDFInput = string | Uint8Array;\n\n/**\n * Helper for KDFs: consumes uint8array or string.\n * When string is passed, does utf8 decoding, using TextDecoder.\n */\nexport function kdfInputToBytes(data: KDFInput, errorTitle = ''): Uint8Array {\n if (typeof data === 'string') return utf8ToBytes(data);\n return abytes(data, undefined, errorTitle);\n}\n\n/** Copies several Uint8Arrays into one. */\nexport function concatBytes(...arrays: Uint8Array[]): Uint8Array {\n let sum = 0;\n for (let i = 0; i < arrays.length; i++) {\n const a = arrays[i];\n abytes(a);\n sum += a.length;\n }\n const res = new Uint8Array(sum);\n for (let i = 0, pad = 0; i < arrays.length; i++) {\n const a = arrays[i];\n res.set(a, pad);\n pad += a.length;\n }\n return res;\n}\n\ntype EmptyObj = {};\n/** Merges default options and passed options. */\nexport function checkOpts<T1 extends EmptyObj, T2 extends EmptyObj>(\n defaults: T1,\n opts?: T2\n): T1 & T2 {\n if (opts !== undefined && {}.toString.call(opts) !== '[object Object]')\n throw new Error('options must be object or undefined');\n const merged = Object.assign(defaults, opts);\n return merged as T1 & T2;\n}\n\n/** Common interface for all hashes. */\nexport interface Hash<T> {\n blockLen: number; // Bytes per block\n outputLen: number; // Bytes in output\n update(buf: Uint8Array): this;\n digestInto(buf: Uint8Array): void;\n digest(): Uint8Array;\n destroy(): void;\n _cloneInto(to?: T): T;\n clone(): T;\n}\n\n/** PseudoRandom (number) Generator */\nexport interface PRG {\n addEntropy(seed: Uint8Array): void;\n randomBytes(length: number): Uint8Array;\n clean(): void;\n}\n\n/**\n * XOF: streaming API to read digest in chunks.\n * Same as 'squeeze' in keccak/k12 and 'seek' in blake3, but more generic name.\n * When hash used in XOF mode it is up to user to call '.destroy' afterwards, since we cannot\n * destroy state, next call can require more bytes.\n */\nexport type HashXOF<T extends Hash<T>> = Hash<T> & {\n xof(bytes: number): Uint8Array; // Read 'bytes' bytes from digest stream\n xofInto(buf: Uint8Array): Uint8Array; // read buf.length bytes from digest stream into buf\n};\n\n/** Hash constructor */\nexport type HasherCons<T, Opts = undefined> = Opts extends undefined ? () => T : (opts?: Opts) => T;\n/** Optional hash params. */\nexport type HashInfo = {\n oid?: Uint8Array; // DER encoded OID in bytes\n};\n/** Hash function */\nexport type CHash<T extends Hash<T> = Hash<any>, Opts = undefined> = {\n outputLen: number;\n blockLen: number;\n} & HashInfo &\n (Opts extends undefined\n ? {\n (msg: Uint8Array): Uint8Array;\n create(): T;\n }\n : {\n (msg: Uint8Array, opts?: Opts): Uint8Array;\n create(opts?: Opts): T;\n });\n/** XOF with output */\nexport type CHashXOF<T extends HashXOF<T> = HashXOF<any>, Opts = undefined> = CHash<T, Opts>;\n\n/** Creates function with outputLen, blockLen, create properties from a class constructor. */\nexport function createHasher<T extends Hash<T>, Opts = undefined>(\n hashCons: HasherCons<T, Opts>,\n info: HashInfo = {}\n): CHash<T, Opts> {\n const hashC: any = (msg: Uint8Array, opts?: Opts) => hashCons(opts).update(msg).digest();\n const tmp = hashCons(undefined);\n hashC.outputLen = tmp.outputLen;\n hashC.blockLen = tmp.blockLen;\n hashC.create = (opts?: Opts) => hashCons(opts);\n Object.assign(hashC, info);\n return Object.freeze(hashC);\n}\n\n/** Cryptographically secure PRNG. Uses internal OS-level `crypto.getRandomValues`. */\nexport function randomBytes(bytesLength = 32): Uint8Array {\n const cr = typeof globalThis === 'object' ? (globalThis as any).crypto : null;\n if (typeof cr?.getRandomValues !== 'function')\n throw new Error('crypto.getRandomValues must be defined');\n return cr.getRandomValues(new Uint8Array(bytesLength));\n}\n\n/** Creates OID opts for NIST hashes, with prefix 06 09 60 86 48 01 65 03 04 02. */\nexport const oidNist = (suffix: number): Required<HashInfo> => ({\n oid: Uint8Array.from([0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, suffix]),\n});\n","/**\n * HMAC: RFC2104 message authentication code.\n * @module\n */\nimport { abytes, aexists, ahash, clean, type CHash, type Hash } from './utils.ts';\n\n/** Internal class for HMAC. */\nexport class _HMAC<T extends Hash<T>> implements Hash<_HMAC<T>> {\n oHash: T;\n iHash: T;\n blockLen: number;\n outputLen: number;\n private finished = false;\n private destroyed = false;\n\n constructor(hash: CHash, key: Uint8Array) {\n ahash(hash);\n abytes(key, undefined, 'key');\n this.iHash = hash.create() as T;\n if (typeof this.iHash.update !== 'function')\n throw new Error('Expected instance of class which extends utils.Hash');\n this.blockLen = this.iHash.blockLen;\n this.outputLen = this.iHash.outputLen;\n const blockLen = this.blockLen;\n const pad = new Uint8Array(blockLen);\n // blockLen can be bigger than outputLen\n pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);\n for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36;\n this.iHash.update(pad);\n // By doing update (processing of first block) of outer hash here we can re-use it between multiple calls via clone\n this.oHash = hash.create() as T;\n // Undo internal XOR && apply outer XOR\n for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36 ^ 0x5c;\n this.oHash.update(pad);\n clean(pad);\n }\n update(buf: Uint8Array): this {\n aexists(this);\n this.iHash.update(buf);\n return this;\n }\n digestInto(out: Uint8Array): void {\n aexists(this);\n abytes(out, this.outputLen, 'output');\n this.finished = true;\n this.iHash.digestInto(out);\n this.oHash.update(out);\n this.oHash.digestInto(out);\n this.destroy();\n }\n digest(): Uint8Array {\n const out = new Uint8Array(this.oHash.outputLen);\n this.digestInto(out);\n return out;\n }\n _cloneInto(to?: _HMAC<T>): _HMAC<T> {\n // Create new instance without calling constructor since key already in state and we don't know it.\n to ||= Object.create(Object.getPrototypeOf(this), {});\n const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;\n to = to as this;\n to.finished = finished;\n to.destroyed = destroyed;\n to.blockLen = blockLen;\n to.outputLen = outputLen;\n to.oHash = oHash._cloneInto(to.oHash);\n to.iHash = iHash._cloneInto(to.iHash);\n return to;\n }\n clone(): _HMAC<T> {\n return this._cloneInto();\n }\n destroy(): void {\n this.destroyed = true;\n this.oHash.destroy();\n this.iHash.destroy();\n }\n}\n\n/**\n * HMAC: RFC2104 message authentication code.\n * @param hash - function that would be used e.g. sha256\n * @param key - message key\n * @param message - message data\n * @example\n * import { hmac } from '@noble/hashes/hmac';\n * import { sha256 } from '@noble/hashes/sha2';\n * const mac1 = hmac(sha256, 'key', 'message');\n */\nexport const hmac: {\n (hash: CHash, key: Uint8Array, message: Uint8Array): Uint8Array;\n create(hash: CHash, key: Uint8Array): _HMAC<any>;\n} = (hash: CHash, key: Uint8Array, message: Uint8Array): Uint8Array =>\n new _HMAC<any>(hash, key).update(message).digest();\nhmac.create = (hash: CHash, key: Uint8Array) => new _HMAC<any>(hash, key);\n","/**\n * HKDF (RFC 5869): extract + expand in one step.\n * See https://soatok.blog/2021/11/17/understanding-hkdf/.\n * @module\n */\nimport { hmac } from './hmac.ts';\nimport { abytes, ahash, anumber, type CHash, clean } from './utils.ts';\n\n/**\n * HKDF-extract from spec. Less important part. `HKDF-Extract(IKM, salt) -> PRK`\n * Arguments position differs from spec (IKM is first one, since it is not optional)\n * @param hash - hash function that would be used (e.g. sha256)\n * @param ikm - input keying material, the initial key\n * @param salt - optional salt value (a non-secret random value)\n */\nexport function extract(hash: CHash, ikm: Uint8Array, salt?: Uint8Array): Uint8Array {\n ahash(hash);\n // NOTE: some libraries treat zero-length array as 'not provided';\n // we don't, since we have undefined as 'not provided'\n // https://github.com/RustCrypto/KDFs/issues/15\n if (salt === undefined) salt = new Uint8Array(hash.outputLen);\n return hmac(hash, salt, ikm);\n}\n\nconst HKDF_COUNTER = /* @__PURE__ */ Uint8Array.of(0);\nconst EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();\n\n/**\n * HKDF-expand from the spec. The most important part. `HKDF-Expand(PRK, info, L) -> OKM`\n * @param hash - hash function that would be used (e.g. sha256)\n * @param prk - a pseudorandom key of at least HashLen octets (usually, the output from the extract step)\n * @param info - optional context and application specific information (can be a zero-length string)\n * @param length - length of output keying material in bytes\n */\nexport function expand(\n hash: CHash,\n prk: Uint8Array,\n info?: Uint8Array,\n length: number = 32\n): Uint8Array {\n ahash(hash);\n anumber(length, 'length');\n const olen = hash.outputLen;\n if (length > 255 * olen) throw new Error('Length must be <= 255*HashLen');\n const blocks = Math.ceil(length / olen);\n if (info === undefined) info = EMPTY_BUFFER;\n else abytes(info, undefined, 'info');\n // first L(ength) octets of T\n const okm = new Uint8Array(blocks * olen);\n // Re-use HMAC instance between blocks\n const HMAC = hmac.create(hash, prk);\n const HMACTmp = HMAC._cloneInto();\n const T = new Uint8Array(HMAC.outputLen);\n for (let counter = 0; counter < blocks; counter++) {\n HKDF_COUNTER[0] = counter + 1;\n // T(0) = empty string (zero length)\n // T(N) = HMAC-Hash(PRK, T(N-1) | info | N)\n HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T)\n .update(info)\n .update(HKDF_COUNTER)\n .digestInto(T);\n okm.set(T, olen * counter);\n HMAC._cloneInto(HMACTmp);\n }\n HMAC.destroy();\n HMACTmp.destroy();\n clean(T, HKDF_COUNTER);\n return okm.slice(0, length);\n}\n\n/**\n * HKDF (RFC 5869): derive keys from an initial input.\n * Combines hkdf_extract + hkdf_expand in one step\n * @param hash - hash function that would be used (e.g. sha256)\n * @param ikm - input keying material, the initial key\n * @param salt - optional salt value (a non-secret random value)\n * @param info - optional context and application specific information (can be a zero-length string)\n * @param length - length of output keying material in bytes\n * @example\n * import { hkdf } from '@noble/hashes/hkdf';\n * import { sha256 } from '@noble/hashes/sha2';\n * import { randomBytes } from '@noble/hashes/utils';\n * const inputKey = randomBytes(32);\n * const salt = randomBytes(32);\n * const info = 'application-key';\n * const hk1 = hkdf(sha256, inputKey, salt, info, 32);\n */\nexport const hkdf = (\n hash: CHash,\n ikm: Uint8Array,\n salt: Uint8Array | undefined,\n info: Uint8Array | undefined,\n length: number\n): Uint8Array => expand(hash, extract(hash, ikm, salt), info, length);\n","/**\n * Internal Merkle-Damgard hash utils.\n * @module\n */\nimport { abytes, aexists, aoutput, clean, createView, type Hash } from './utils.ts';\n\n/** Choice: a ? b : c */\nexport function Chi(a: number, b: number, c: number): number {\n return (a & b) ^ (~a & c);\n}\n\n/** Majority function, true if any two inputs is true. */\nexport function Maj(a: number, b: number, c: number): number {\n return (a & b) ^ (a & c) ^ (b & c);\n}\n\n/**\n * Merkle-Damgard hash construction base class.\n * Could be used to create MD5, RIPEMD, SHA1, SHA2.\n */\nexport abstract class HashMD<T extends HashMD<T>> implements Hash<T> {\n protected abstract process(buf: DataView, offset: number): void;\n protected abstract get(): number[];\n protected abstract set(...args: number[]): void;\n abstract destroy(): void;\n protected abstract roundClean(): void;\n\n readonly blockLen: number;\n readonly outputLen: number;\n readonly padOffset: number;\n readonly isLE: boolean;\n\n // For partial updates less than block size\n protected buffer: Uint8Array;\n protected view: DataView;\n protected finished = false;\n protected length = 0;\n protected pos = 0;\n protected destroyed = false;\n\n constructor(blockLen: number, outputLen: number, padOffset: number, isLE: boolean) {\n this.blockLen = blockLen;\n this.outputLen = outputLen;\n this.padOffset = padOffset;\n this.isLE = isLE;\n this.buffer = new Uint8Array(blockLen);\n this.view = createView(this.buffer);\n }\n update(data: Uint8Array): this {\n aexists(this);\n abytes(data);\n const { view, buffer, blockLen } = this;\n const len = data.length;\n for (let pos = 0; pos < len; ) {\n const take = Math.min(blockLen - this.pos, len - pos);\n // Fast path: we have at least one block in input, cast it to view and process\n if (take === blockLen) {\n const dataView = createView(data);\n for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);\n continue;\n }\n buffer.set(data.subarray(pos, pos + take), this.pos);\n this.pos += take;\n pos += take;\n if (this.pos === blockLen) {\n this.process(view, 0);\n this.pos = 0;\n }\n }\n this.length += data.length;\n this.roundClean();\n return this;\n }\n digestInto(out: Uint8Array): void {\n aexists(this);\n aoutput(out, this);\n this.finished = true;\n // Padding\n // We can avoid allocation of buffer for padding completely if it\n // was previously not allocated here. But it won't change performance.\n const { buffer, view, blockLen, isLE } = this;\n let { pos } = this;\n // append the bit '1' to the message\n buffer[pos++] = 0b10000000;\n clean(this.buffer.subarray(pos));\n // we have less than padOffset left in buffer, so we cannot put length in\n // current block, need process it and pad again\n if (this.padOffset > blockLen - pos) {\n this.process(view, 0);\n pos = 0;\n }\n // Pad until full block byte with zeros\n for (let i = pos; i < blockLen; i++) buffer[i] = 0;\n // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that\n // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.\n // So we just write lowest 64 bits of that value.\n view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);\n this.process(view, 0);\n const oview = createView(out);\n const len = this.outputLen;\n // NOTE: we do division by 4 later, which must be fused in single op with modulo by JIT\n if (len % 4) throw new Error('_sha2: outputLen must be aligned to 32bit');\n const outLen = len / 4;\n const state = this.get();\n if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state');\n for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);\n }\n digest(): Uint8Array {\n const { buffer, outputLen } = this;\n this.digestInto(buffer);\n const res = buffer.slice(0, outputLen);\n this.destroy();\n return res;\n }\n _cloneInto(to?: T): T {\n to ||= new (this.constructor as any)() as T;\n to.set(...this.get());\n const { blockLen, buffer, length, finished, destroyed, pos } = this;\n to.destroyed = destroyed;\n to.finished = finished;\n to.length = length;\n to.pos = pos;\n if (length % blockLen) to.buffer.set(buffer);\n return to as unknown as any;\n }\n clone(): T {\n return this._cloneInto();\n }\n}\n\n/**\n * Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.\n * Check out `test/misc/sha2-gen-iv.js` for recomputation guide.\n */\n\n/** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */\nexport const SHA256_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,\n]);\n\n/** Initial SHA224 state. Bits 32..64 of frac part of sqrt of primes 23..53 */\nexport const SHA224_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([\n 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4,\n]);\n\n/** Initial SHA384 state. Bits 0..64 of frac part of sqrt of primes 23..53 */\nexport const SHA384_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([\n 0xcbbb9d5d, 0xc1059ed8, 0x629a292a, 0x367cd507, 0x9159015a, 0x3070dd17, 0x152fecd8, 0xf70e5939,\n 0x67332667, 0xffc00b31, 0x8eb44a87, 0x68581511, 0xdb0c2e0d, 0x64f98fa7, 0x47b5481d, 0xbefa4fa4,\n]);\n\n/** Initial SHA512 state. Bits 0..64 of frac part of sqrt of primes 2..19 */\nexport const SHA512_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([\n 0x6a09e667, 0xf3bcc908, 0xbb67ae85, 0x84caa73b, 0x3c6ef372, 0xfe94f82b, 0xa54ff53a, 0x5f1d36f1,\n 0x510e527f, 0xade682d1, 0x9b05688c, 0x2b3e6c1f, 0x1f83d9ab, 0xfb41bd6b, 0x5be0cd19, 0x137e2179,\n]);\n","/**\n * SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.\n * SHA256 is the fastest hash implementable in JS, even faster than Blake3.\n * Check out [RFC 4634](https://www.rfc-editor.org/rfc/rfc4634) and\n * [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).\n * @module\n */\nimport { Chi, HashMD, Maj, SHA224_IV, SHA256_IV, SHA384_IV, SHA512_IV } from './_md.ts';\nimport * as u64 from './_u64.ts';\nimport { type CHash, clean, createHasher, oidNist, rotr } from './utils.ts';\n\n/**\n * Round constants:\n * First 32 bits of fractional parts of the cube roots of the first 64 primes 2..311)\n */\n// prettier-ignore\nconst SHA256_K = /* @__PURE__ */ Uint32Array.from([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n]);\n\n/** Reusable temporary buffer. \"W\" comes straight from spec. */\nconst SHA256_W = /* @__PURE__ */ new Uint32Array(64);\n\n/** Internal 32-byte base SHA2 hash class. */\nabstract class SHA2_32B<T extends SHA2_32B<T>> extends HashMD<T> {\n // We cannot use array here since array allows indexing by variable\n // which means optimizer/compiler cannot use registers.\n protected abstract A: number;\n protected abstract B: number;\n protected abstract C: number;\n protected abstract D: number;\n protected abstract E: number;\n protected abstract F: number;\n protected abstract G: number;\n protected abstract H: number;\n\n constructor(outputLen: number) {\n super(64, outputLen, 8, false);\n }\n protected get(): [number, number, number, number, number, number, number, number] {\n const { A, B, C, D, E, F, G, H } = this;\n return [A, B, C, D, E, F, G, H];\n }\n // prettier-ignore\n protected set(\n A: number, B: number, C: number, D: number, E: number, F: number, G: number, H: number\n ): void {\n this.A = A | 0;\n this.B = B | 0;\n this.C = C | 0;\n this.D = D | 0;\n this.E = E | 0;\n this.F = F | 0;\n this.G = G | 0;\n this.H = H | 0;\n }\n protected process(view: DataView, offset: number): void {\n // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array\n for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false);\n for (let i = 16; i < 64; i++) {\n const W15 = SHA256_W[i - 15];\n const W2 = SHA256_W[i - 2];\n const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);\n const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);\n SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;\n }\n // Compression function main loop, 64 rounds\n let { A, B, C, D, E, F, G, H } = this;\n for (let i = 0; i < 64; i++) {\n const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);\n const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;\n const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);\n const T2 = (sigma0 + Maj(A, B, C)) | 0;\n H = G;\n G = F;\n F = E;\n E = (D + T1) | 0;\n D = C;\n C = B;\n B = A;\n A = (T1 + T2) | 0;\n }\n // Add the compressed chunk to the current hash value\n A = (A + this.A) | 0;\n B = (B + this.B) | 0;\n C = (C + this.C) | 0;\n D = (D + this.D) | 0;\n E = (E + this.E) | 0;\n F = (F + this.F) | 0;\n G = (G + this.G) | 0;\n H = (H + this.H) | 0;\n this.set(A, B, C, D, E, F, G, H);\n }\n protected roundClean(): void {\n clean(SHA256_W);\n }\n destroy(): void {\n this.set(0, 0, 0, 0, 0, 0, 0, 0);\n clean(this.buffer);\n }\n}\n\n/** Internal SHA2-256 hash class. */\nexport class _SHA256 extends SHA2_32B<_SHA256> {\n // We cannot use array here since array allows indexing by variable\n // which means optimizer/compiler cannot use registers.\n protected A: number = SHA256_IV[0] | 0;\n protected B: number = SHA256_IV[1] | 0;\n protected C: number = SHA256_IV[2] | 0;\n protected D: number = SHA256_IV[3] | 0;\n protected E: number = SHA256_IV[4] | 0;\n protected F: number = SHA256_IV[5] | 0;\n protected G: number = SHA256_IV[6] | 0;\n protected H: number = SHA256_IV[7] | 0;\n constructor() {\n super(32);\n }\n}\n\n/** Internal SHA2-224 hash class. */\nexport class _SHA224 extends SHA2_32B<_SHA224> {\n protected A: number = SHA224_IV[0] | 0;\n protected B: number = SHA224_IV[1] | 0;\n protected C: number = SHA224_IV[2] | 0;\n protected D: number = SHA224_IV[3] | 0;\n protected E: number = SHA224_IV[4] | 0;\n protected F: number = SHA224_IV[5] | 0;\n protected G: number = SHA224_IV[6] | 0;\n protected H: number = SHA224_IV[7] | 0;\n constructor() {\n super(28);\n }\n}\n\n// SHA2-512 is slower than sha256 in js because u64 operations are slow.\n\n// Round contants\n// First 32 bits of the fractional parts of the cube roots of the first 80 primes 2..409\n// prettier-ignore\nconst K512 = /* @__PURE__ */ (() => u64.split([\n '0x428a2f98d728ae22', '0x7137449123ef65cd', '0xb5c0fbcfec4d3b2f', '0xe9b5dba58189dbbc',\n '0x3956c25bf348b538', '0x59f111f1b605d019', '0x923f82a4af194f9b', '0xab1c5ed5da6d8118',\n '0xd807aa98a3030242', '0x12835b0145706fbe', '0x243185be4ee4b28c', '0x550c7dc3d5ffb4e2',\n '0x72be5d74f27b896f', '0x80deb1fe3b1696b1', '0x9bdc06a725c71235', '0xc19bf174cf692694',\n '0xe49b69c19ef14ad2', '0xefbe4786384f25e3', '0x0fc19dc68b8cd5b5', '0x240ca1cc77ac9c65',\n '0x2de92c6f592b0275', '0x4a7484aa6ea6e483', '0x5cb0a9dcbd41fbd4', '0x76f988da831153b5',\n '0x983e5152ee66dfab', '0xa831c66d2db43210', '0xb00327c898fb213f', '0xbf597fc7beef0ee4',\n '0xc6e00bf33da88fc2', '0xd5a79147930aa725', '0x06ca6351e003826f', '0x142929670a0e6e70',\n '0x27b70a8546d22ffc', '0x2e1b21385c26c926', '0x4d2c6dfc5ac42aed', '0x53380d139d95b3df',\n '0x650a73548baf63de', '0x766a0abb3c77b2a8', '0x81c2c92e47edaee6', '0x92722c851482353b',\n '0xa2bfe8a14cf10364', '0xa81a664bbc423001', '0xc24b8b70d0f89791', '0xc76c51a30654be30',\n '0xd192e819d6ef5218', '0xd69906245565a910', '0xf40e35855771202a', '0x106aa07032bbd1b8',\n '0x19a4c116b8d2d0c8', '0x1e376c085141ab53', '0x2748774cdf8eeb99', '0x34b0bcb5e19b48a8',\n '0x391c0cb3c5c95a63', '0x4ed8aa4ae3418acb', '0x5b9cca4f7763e373', '0x682e6ff3d6b2b8a3',\n '0x748f82ee5defb2fc', '0x78a5636f43172f60', '0x84c87814a1f0ab72', '0x8cc702081a6439ec',\n '0x90befffa23631e28', '0xa4506cebde82bde9', '0xbef9a3f7b2c67915', '0xc67178f2e372532b',\n '0xca273eceea26619c', '0xd186b8c721c0c207', '0xeada7dd6cde0eb1e', '0xf57d4f7fee6ed178',\n '0x06f067aa72176fba', '0x0a637dc5a2c898a6', '0x113f9804bef90dae', '0x1b710b35131c471b',\n '0x28db77f523047d84', '0x32caab7b40c72493', '0x3c9ebe0a15c9bebc', '0x431d67c49c100d4c',\n '0x4cc5d4becb3e42b6', '0x597f299cfc657e2a', '0x5fcb6fab3ad6faec', '0x6c44198c4a475817'\n].map(n => BigInt(n))))();\nconst SHA512_Kh = /* @__PURE__ */ (() => K512[0])();\nconst SHA512_Kl = /* @__PURE__ */ (() => K512[1])();\n\n// Reusable temporary buffers\nconst SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);\nconst SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);\n\n/** Internal 64-byte base SHA2 hash class. */\nabstract class SHA2_64B<T extends SHA2_64B<T>> extends HashMD<T> {\n // We cannot use array here since array allows indexing by variable\n // which means optimizer/compiler cannot use registers.\n // h -- high 32 bits, l -- low 32 bits\n protected abstract Ah: number;\n protected abstract Al: number;\n protected abstract Bh: number;\n protected abstract Bl: number;\n protected abstract Ch: number;\n protected abstract Cl: number;\n protected abstract Dh: number;\n protected abstract Dl: number;\n protected abstract Eh: number;\n protected abstract El: number;\n protected abstract Fh: number;\n protected abstract Fl: number;\n protected abstract Gh: number;\n protected abstract Gl: number;\n protected abstract Hh: number;\n protected abstract Hl: number;\n\n constructor(outputLen: number) {\n super(128, outputLen, 16, false);\n }\n // prettier-ignore\n protected get(): [\n number, number, number, number, number, number, number, number,\n number, number, number, number, number, number, number, number\n ] {\n const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;\n return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];\n }\n // prettier-ignore\n protected set(\n Ah: number, Al: number, Bh: number, Bl: number, Ch: number, Cl: number, Dh: number, Dl: number,\n Eh: number, El: number, Fh: number, Fl: number, Gh: number, Gl: number, Hh: number, Hl: number\n ): void {\n this.Ah = Ah | 0;\n this.Al = Al | 0;\n this.Bh = Bh | 0;\n this.Bl = Bl | 0;\n this.Ch = Ch | 0;\n this.Cl = Cl | 0;\n this.Dh = Dh | 0;\n this.Dl = Dl | 0;\n this.Eh = Eh | 0;\n this.El = El | 0;\n this.Fh = Fh | 0;\n this.Fl = Fl | 0;\n this.Gh = Gh | 0;\n this.Gl = Gl | 0;\n this.Hh = Hh | 0;\n this.Hl = Hl | 0;\n }\n protected process(view: DataView, offset: number): void {\n // Extend the first 16 words into the remaining 64 words w[16..79] of the message schedule array\n for (let i = 0; i < 16; i++, offset += 4) {\n SHA512_W_H[i] = view.getUint32(offset);\n SHA512_W_L[i] = view.getUint32((offset += 4));\n }\n for (let i = 16; i < 80; i++) {\n // s0 := (w[i-15] rightrotate 1) xor (w[i-15] rightrotate 8) xor (w[i-15] rightshift 7)\n const W15h = SHA512_W_H[i - 15] | 0;\n const W15l = SHA512_W_L[i - 15] | 0;\n const s0h = u64.rotrSH(W15h, W15l, 1) ^ u64.rotrSH(W15h, W15l, 8) ^ u64.shrSH(W15h, W15l, 7);\n const s0l = u64.rotrSL(W15h, W15l, 1) ^ u64.rotrSL(W15h, W15l, 8) ^ u64.shrSL(W15h, W15l, 7);\n // s1 := (w[i-2] rightrotate 19) xor (w[i-2] rightrotate 61) xor (w[i-2] rightshift 6)\n const W2h = SHA512_W_H[i - 2] | 0;\n const W2l = SHA512_W_L[i - 2] | 0;\n const s1h = u64.rotrSH(W2h, W2l, 19) ^ u64.rotrBH(W2h, W2l, 61) ^ u64.shrSH(W2h, W2l, 6);\n const s1l = u64.rotrSL(W2h, W2l, 19) ^ u64.rotrBL(W2h, W2l, 61) ^ u64.shrSL(W2h, W2l, 6);\n // SHA256_W[i] = s0 + s1 + SHA256_W[i - 7] + SHA256_W[i - 16];\n const SUMl = u64.add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);\n const SUMh = u64.add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);\n SHA512_W_H[i] = SUMh | 0;\n SHA512_W_L[i] = SUMl | 0;\n }\n let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;\n // Compression function main loop, 80 rounds\n for (let i = 0; i < 80; i++) {\n // S1 := (e rightrotate 14) xor (e rightrotate 18) xor (e rightrotate 41)\n const sigma1h = u64.rotrSH(Eh, El, 14) ^ u64.rotrSH(Eh, El, 18) ^ u64.rotrBH(Eh, El, 41);\n const sigma1l = u64.rotrSL(Eh, El, 14) ^ u64.rotrSL(Eh, El, 18) ^ u64.rotrBL(Eh, El, 41);\n //const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;\n const CHIh = (Eh & Fh) ^ (~Eh & Gh);\n const CHIl = (El & Fl) ^ (~El & Gl);\n // T1 = H + sigma1 + Chi(E, F, G) + SHA512_K[i] + SHA512_W[i]\n // prettier-ignore\n const T1ll = u64.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);\n const T1h = u64.add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);\n const T1l = T1ll | 0;\n // S0 := (a rightrotate 28) xor (a rightrotate 34) xor (a rightrotate 39)\n const sigma0h = u64.rotrSH(Ah, Al, 28) ^ u64.rotrBH(Ah, Al, 34) ^ u64.rotrBH(Ah, Al, 39);\n const sigma0l = u64.rotrSL(Ah, Al, 28) ^ u64.rotrBL(Ah, Al, 34) ^ u64.rotrBL(Ah, Al, 39);\n const MAJh = (Ah & Bh) ^ (Ah & Ch) ^ (Bh & Ch);\n const MAJl = (Al & Bl) ^ (Al & Cl) ^ (Bl & Cl);\n Hh = Gh | 0;\n Hl = Gl | 0;\n Gh = Fh | 0;\n Gl = Fl | 0;\n Fh = Eh | 0;\n Fl = El | 0;\n ({ h: Eh, l: El } = u64.add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));\n Dh = Ch | 0;\n Dl = Cl | 0;\n Ch = Bh | 0;\n Cl = Bl | 0;\n Bh = Ah | 0;\n Bl = Al | 0;\n const All = u64.add3L(T1l, sigma0l, MAJl);\n Ah = u64.add3H(All, T1h, sigma0h, MAJh);\n Al = All | 0;\n }\n // Add the compressed chunk to the current hash value\n ({ h: Ah, l: Al } = u64.add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));\n ({ h: Bh, l: Bl } = u64.add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));\n ({ h: Ch, l: Cl } = u64.add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));\n ({ h: Dh, l: Dl } = u64.add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));\n ({ h: Eh, l: El } = u64.add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));\n ({ h: Fh, l: Fl } = u64.add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));\n ({ h: Gh, l: Gl } = u64.add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));\n ({ h: Hh, l: Hl } = u64.add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));\n this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);\n }\n protected roundClean(): void {\n clean(SHA512_W_H, SHA512_W_L);\n }\n destroy(): void {\n clean(this.buffer);\n this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n }\n}\n\n/** Internal SHA2-512 hash class. */\nexport class _SHA512 extends SHA2_64B<_SHA512> {\n protected Ah: number = SHA512_IV[0] | 0;\n protected Al: number = SHA512_IV[1] | 0;\n protected Bh: number = SHA512_IV[2] | 0;\n protected Bl: number = SHA512_IV[3] | 0;\n protected Ch: number = SHA512_IV[4] | 0;\n protected Cl: number = SHA512_IV[5] | 0;\n protected Dh: number = SHA512_IV[6] | 0;\n protected Dl: number = SHA512_IV[7] | 0;\n protected Eh: number = SHA512_IV[8] | 0;\n protected El: number = SHA512_IV[9] | 0;\n protected Fh: number = SHA512_IV[10] | 0;\n protected Fl: number = SHA512_IV[11] | 0;\n protected Gh: number = SHA512_IV[12] | 0;\n protected Gl: number = SHA512_IV[13] | 0;\n protected Hh: number = SHA512_IV[14] | 0;\n protected Hl: number = SHA512_IV[15] | 0;\n\n constructor() {\n super(64);\n }\n}\n\n/** Internal SHA2-384 hash class. */\nexport class _SHA384 extends SHA2_64B<_SHA384> {\n protected Ah: number = SHA384_IV[0] | 0;\n protected Al: number = SHA384_IV[1] | 0;\n protected Bh: number = SHA384_IV[2] | 0;\n protected Bl: number = SHA384_IV[3] | 0;\n protected Ch: number = SHA384_IV[4] | 0;\n protected Cl: number = SHA384_IV[5] | 0;\n protected Dh: number = SHA384_IV[6] | 0;\n protected Dl: number = SHA384_IV[7] | 0;\n protected Eh: number = SHA384_IV[8] | 0;\n protected El: number = SHA384_IV[9] | 0;\n protected Fh: number = SHA384_IV[10] | 0;\n protected Fl: number = SHA384_IV[11] | 0;\n protected Gh: number = SHA384_IV[12] | 0;\n protected Gl: number = SHA384_IV[13] | 0;\n protected Hh: number = SHA384_IV[14] | 0;\n protected Hl: number = SHA384_IV[15] | 0;\n\n constructor() {\n super(48);\n }\n}\n\n/**\n * Truncated SHA512/256 and SHA512/224.\n * SHA512_IV is XORed with 0xa5a5a5a5a5a5a5a5, then used as \"intermediary\" IV of SHA512/t.\n * Then t hashes string to produce result IV.\n * See `test/misc/sha2-gen-iv.js`.\n */\n\n/** SHA512/224 IV */\nconst T224_IV = /* @__PURE__ */ Uint32Array.from([\n 0x8c3d37c8, 0x19544da2, 0x73e19966, 0x89dcd4d6, 0x1dfab7ae, 0x32ff9c82, 0x679dd514, 0x582f9fcf,\n 0x0f6d2b69, 0x7bd44da8, 0x77e36f73, 0x04c48942, 0x3f9d85a8, 0x6a1d36c8, 0x1112e6ad, 0x91d692a1,\n]);\n\n/** SHA512/256 IV */\nconst T256_IV = /* @__PURE__ */ Uint32Array.from([\n 0x22312194, 0xfc2bf72c, 0x9f555fa3, 0xc84c64c2, 0x2393b86b, 0x6f53b151, 0x96387719, 0x5940eabd,\n 0x96283ee2, 0xa88effe3, 0xbe5e1e25, 0x53863992, 0x2b0199fc, 0x2c85b8aa, 0x0eb72ddc, 0x81c52ca2,\n]);\n\n/** Internal SHA2-512/224 hash class. */\nexport class _SHA512_224 extends SHA2_64B<_SHA512_224> {\n protected Ah: number = T224_IV[0] | 0;\n protected Al: number = T224_IV[1] | 0;\n protected Bh: number = T224_IV[2] | 0;\n protected Bl: number = T224_IV[3] | 0;\n protected Ch: number = T224_IV[4] | 0;\n protected Cl: number = T224_IV[5] | 0;\n protected Dh: number = T224_IV[6] | 0;\n protected Dl: number = T224_IV[7] | 0;\n protected Eh: number = T224_IV[8] | 0;\n protected El: number = T224_IV[9] | 0;\n protected Fh: number = T224_IV[10] | 0;\n protected Fl: number = T224_IV[11] | 0;\n protected Gh: number = T224_IV[12] | 0;\n protected Gl: number = T224_IV[13] | 0;\n protected Hh: number = T224_IV[14] | 0;\n protected Hl: number = T224_IV[15] | 0;\n\n constructor() {\n super(28);\n }\n}\n\n/** Internal SHA2-512/256 hash class. */\nexport class _SHA512_256 extends SHA2_64B<_SHA512_256> {\n protected Ah: number = T256_IV[0] | 0;\n protected Al: number = T256_IV[1] | 0;\n protected Bh: number = T256_IV[2] | 0;\n protected Bl: number = T256_IV[3] | 0;\n protected Ch: number = T256_IV[4] | 0;\n protected Cl: number = T256_IV[5] | 0;\n protected Dh: number = T256_IV[6] | 0;\n protected Dl: number = T256_IV[7] | 0;\n protected Eh: number = T256_IV[8] | 0;\n protected El: number = T256_IV[9] | 0;\n protected Fh: number = T256_IV[10] | 0;\n protected Fl: number = T256_IV[11] | 0;\n protected Gh: number = T256_IV[12] | 0;\n protected Gl: number = T256_IV[13] | 0;\n protected Hh: number = T256_IV[14] | 0;\n protected Hl: number = T256_IV[15] | 0;\n\n constructor() {\n super(32);\n }\n}\n\n/**\n * SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info:\n *\n * - Trying 2^128 hashes would get 50% chance of collision, using birthday attack.\n * - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.\n * - Each sha256 hash is executing 2^18 bit operations.\n * - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.\n */\nexport const sha256: CHash<_SHA256> = /* @__PURE__ */ createHasher(\n () => new _SHA256(),\n /* @__PURE__ */ oidNist(0x01)\n);\n/** SHA2-224 hash function from RFC 4634 */\nexport const sha224: CHash<_SHA224> = /* @__PURE__ */ createHasher(\n () => new _SHA224(),\n /* @__PURE__ */ oidNist(0x04)\n);\n\n/** SHA2-512 hash function from RFC 4634. */\nexport const sha512: CHash<_SHA512> = /* @__PURE__ */ createHasher(\n () => new _SHA512(),\n /* @__PURE__ */ oidNist(0x03)\n);\n/** SHA2-384 hash function from RFC 4634. */\nexport const sha384: CHash<_SHA384> = /* @__PURE__ */ createHasher(\n () => new _SHA384(),\n /* @__PURE__ */ oidNist(0x02)\n);\n\n/**\n * SHA2-512/256 \"truncated\" hash function, with improved resistance to length extension attacks.\n * See the paper on [truncated SHA512](https://eprint.iacr.org/2010/548.pdf).\n */\nexport const sha512_256: CHash<_SHA512_256> = /* @__PURE__ */ createHasher(\n () => new _SHA512_256(),\n /* @__PURE__ */ oidNist(0x06)\n);\n/**\n * SHA2-512/224 \"truncated\" hash function, with improved resistance to length extension attacks.\n * See the paper on [truncated SHA512](https://eprint.iacr.org/2010/548.pdf).\n */\nexport const sha512_224: CHash<_SHA512_224> = /* @__PURE__ */ createHasher(\n () => new _SHA512_224(),\n /* @__PURE__ */ oidNist(0x05)\n);\n","/**\n * Cryptographic utilities for SDK2\n *\n * Provides BIP39 mnemonic and BIP32 key derivation functions.\n * Platform-independent - no browser-specific APIs.\n */\n\nimport * as bip39 from 'bip39';\nimport CryptoJS from 'crypto-js';\nimport elliptic from 'elliptic';\nimport { encodeBech32 } from './bech32';\nimport { SphereError } from './errors';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst ec = new elliptic.ec('secp256k1');\n\n/** secp256k1 curve order */\nconst CURVE_ORDER = BigInt(\n '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'\n);\n\n/** Default derivation path for Unicity (BIP44) */\nexport const DEFAULT_DERIVATION_PATH = \"m/44'/0'/0'\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface MasterKey {\n privateKey: string;\n chainCode: string;\n}\n\nexport interface DerivedKey {\n privateKey: string;\n chainCode: string;\n}\n\nexport interface KeyPair {\n privateKey: string;\n publicKey: string;\n}\n\nexport interface AddressInfo extends KeyPair {\n address: string;\n path: string;\n index: number;\n}\n\n// =============================================================================\n// BIP39 Mnemonic Functions\n// =============================================================================\n\n/**\n * Generate a new BIP39 mnemonic phrase\n * @param strength - Entropy bits (128 = 12 words, 256 = 24 words)\n */\nexport function generateMnemonic(strength: 128 | 256 = 128): string {\n return bip39.generateMnemonic(strength);\n}\n\n/**\n * Validate a BIP39 mnemonic phrase\n */\nexport function validateMnemonic(mnemonic: string): boolean {\n return bip39.validateMnemonic(mnemonic);\n}\n\n/**\n * Convert mnemonic to seed (64-byte hex string)\n * @param mnemonic - BIP39 mnemonic phrase\n * @param passphrase - Optional passphrase for additional security\n */\nexport async function mnemonicToSeed(\n mnemonic: string,\n passphrase: string = ''\n): Promise<string> {\n const seedBuffer = await bip39.mnemonicToSeed(mnemonic, passphrase);\n return Buffer.from(seedBuffer).toString('hex');\n}\n\n/**\n * Synchronous version of mnemonicToSeed\n */\nexport function mnemonicToSeedSync(\n mnemonic: string,\n passphrase: string = ''\n): string {\n const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, passphrase);\n return Buffer.from(seedBuffer).toString('hex');\n}\n\n/**\n * Convert mnemonic to entropy (for recovery purposes)\n */\nexport function mnemonicToEntropy(mnemonic: string): string {\n return bip39.mnemonicToEntropy(mnemonic);\n}\n\n/**\n * Convert entropy to mnemonic\n */\nexport function entropyToMnemonic(entropy: string): string {\n return bip39.entropyToMnemonic(entropy);\n}\n\n// =============================================================================\n// BIP32 Key Derivation\n// =============================================================================\n\n/**\n * Generate master key from seed (BIP32 standard)\n * Uses HMAC-SHA512 with key \"Bitcoin seed\"\n */\nexport function generateMasterKey(seedHex: string): MasterKey {\n const I = CryptoJS.HmacSHA512(\n CryptoJS.enc.Hex.parse(seedHex),\n CryptoJS.enc.Utf8.parse('Bitcoin seed')\n ).toString();\n\n const IL = I.substring(0, 64); // Left 32 bytes - master private key\n const IR = I.substring(64); // Right 32 bytes - master chain code\n\n // Validate master key\n const masterKeyBigInt = BigInt('0x' + IL);\n if (masterKeyBigInt === 0n || masterKeyBigInt >= CURVE_ORDER) {\n throw new SphereError('Invalid master key generated', 'VALIDATION_ERROR');\n }\n\n return {\n privateKey: IL,\n chainCode: IR,\n };\n}\n\n/**\n * Derive child key using BIP32 standard\n * @param parentPrivKey - Parent private key (64 hex chars)\n * @param parentChainCode - Parent chain code (64 hex chars)\n * @param index - Child index (>= 0x80000000 for hardened)\n */\nexport function deriveChildKey(\n parentPrivKey: string,\n parentChainCode: string,\n index: number\n): DerivedKey {\n const isHardened = index >= 0x80000000;\n let data: string;\n\n if (isHardened) {\n // Hardened derivation: 0x00 || parentPrivKey || index\n const indexHex = index.toString(16).padStart(8, '0');\n data = '00' + parentPrivKey + indexHex;\n } else {\n // Non-hardened derivation: compressedPubKey || index\n const keyPair = ec.keyFromPrivate(parentPrivKey, 'hex');\n const compressedPubKey = keyPair.getPublic(true, 'hex');\n const indexHex = index.toString(16).padStart(8, '0');\n data = compressedPubKey + indexHex;\n }\n\n // HMAC-SHA512 with chain code as key\n const I = CryptoJS.HmacSHA512(\n CryptoJS.enc.Hex.parse(data),\n CryptoJS.enc.Hex.parse(parentChainCode)\n ).toString();\n\n const IL = I.substring(0, 64); // Left 32 bytes\n const IR = I.substring(64); // Right 32 bytes (new chain code)\n\n // Add IL to parent key mod n (curve order)\n const ilBigInt = BigInt('0x' + IL);\n const parentKeyBigInt = BigInt('0x' + parentPrivKey);\n\n // Check IL is valid (less than curve order)\n if (ilBigInt >= CURVE_ORDER) {\n throw new SphereError('Invalid key: IL >= curve order', 'VALIDATION_ERROR');\n }\n\n const childKeyBigInt = (ilBigInt + parentKeyBigInt) % CURVE_ORDER;\n\n // Check child key is valid (not zero)\n if (childKeyBigInt === 0n) {\n throw new SphereError('Invalid key: child key is zero', 'VALIDATION_ERROR');\n }\n\n const childPrivKey = childKeyBigInt.toString(16).padStart(64, '0');\n\n return {\n privateKey: childPrivKey,\n chainCode: IR,\n };\n}\n\n/**\n * Derive key at a full BIP32/BIP44 path\n * @param masterPrivKey - Master private key\n * @param masterChainCode - Master chain code\n * @param path - BIP44 path like \"m/44'/0'/0'/0/0\"\n */\nexport function deriveKeyAtPath(\n masterPrivKey: string,\n masterChainCode: string,\n path: string\n): DerivedKey {\n const pathParts = path.replace('m/', '').split('/');\n\n let currentKey = masterPrivKey;\n let currentChainCode = masterChainCode;\n\n for (const part of pathParts) {\n const isHardened = part.endsWith(\"'\") || part.endsWith('h');\n const indexStr = part.replace(/['h]$/, '');\n let index = parseInt(indexStr, 10);\n\n if (isHardened) {\n index += 0x80000000; // Add hardened offset\n }\n\n const derived = deriveChildKey(currentKey, currentChainCode, index);\n currentKey = derived.privateKey;\n currentChainCode = derived.chainCode;\n }\n\n return {\n privateKey: currentKey,\n chainCode: currentChainCode,\n };\n}\n\n// =============================================================================\n// Key Pair Operations\n// =============================================================================\n\n/**\n * Get public key from private key\n * @param privateKey - Private key as hex string\n * @param compressed - Return compressed public key (default: true)\n */\nexport function getPublicKey(privateKey: string, compressed: boolean = true): string {\n const keyPair = ec.keyFromPrivate(privateKey, 'hex');\n return keyPair.getPublic(compressed, 'hex');\n}\n\n/**\n * Create key pair from private key\n */\nexport function createKeyPair(privateKey: string): KeyPair {\n return {\n privateKey,\n publicKey: getPublicKey(privateKey),\n };\n}\n\n// =============================================================================\n// Hash Functions\n// =============================================================================\n\n/**\n * Compute SHA256 hash\n */\nexport function sha256(data: string, inputEncoding: 'hex' | 'utf8' = 'hex'): string {\n const parsed =\n inputEncoding === 'hex'\n ? CryptoJS.enc.Hex.parse(data)\n : CryptoJS.enc.Utf8.parse(data);\n return CryptoJS.SHA256(parsed).toString();\n}\n\n/**\n * Compute RIPEMD160 hash\n */\nexport function ripemd160(data: string, inputEncoding: 'hex' | 'utf8' = 'hex'): string {\n const parsed =\n inputEncoding === 'hex'\n ? CryptoJS.enc.Hex.parse(data)\n : CryptoJS.enc.Utf8.parse(data);\n return CryptoJS.RIPEMD160(parsed).toString();\n}\n\n/**\n * Compute HASH160 (SHA256 -> RIPEMD160)\n */\nexport function hash160(data: string): string {\n const sha = sha256(data, 'hex');\n return ripemd160(sha, 'hex');\n}\n\n/**\n * Compute double SHA256\n */\nexport function doubleSha256(data: string, inputEncoding: 'hex' | 'utf8' = 'hex'): string {\n const first = sha256(data, inputEncoding);\n return sha256(first, 'hex');\n}\n\n/**\n * Alias for hash160 (L1 SDK compatibility)\n */\nexport const computeHash160 = hash160;\n\n/**\n * Convert hex string to Uint8Array for witness program\n */\nexport function hash160ToBytes(hash160Hex: string): Uint8Array {\n const matches = hash160Hex.match(/../g);\n if (!matches) return new Uint8Array(0);\n return Uint8Array.from(matches.map((x) => parseInt(x, 16)));\n}\n\n/**\n * Generate bech32 address from public key\n * @param publicKey - Compressed public key as hex string\n * @param prefix - Address prefix (default: \"alpha\")\n * @param witnessVersion - Witness version (default: 0 for P2WPKH)\n * @returns Bech32 encoded address\n */\nexport function publicKeyToAddress(\n publicKey: string,\n prefix: string = 'alpha',\n witnessVersion: number = 0\n): string {\n const pubKeyHash = hash160(publicKey);\n const programBytes = hash160ToBytes(pubKeyHash);\n return encodeBech32(prefix, witnessVersion, programBytes);\n}\n\n/**\n * Get address info from private key\n */\nexport function privateKeyToAddressInfo(\n privateKey: string,\n prefix: string = 'alpha'\n): { address: string; publicKey: string } {\n const publicKey = getPublicKey(privateKey);\n const address = publicKeyToAddress(publicKey, prefix);\n return { address, publicKey };\n}\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Convert hex string to Uint8Array\n */\nexport function hexToBytes(hex: string): Uint8Array {\n const matches = hex.match(/../g);\n if (!matches) {\n return new Uint8Array(0);\n }\n return Uint8Array.from(matches.map((x) => parseInt(x, 16)));\n}\n\n/**\n * Convert Uint8Array to hex string\n */\nexport function bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Generate random bytes as hex string\n */\nexport function randomBytes(length: number): string {\n const words = CryptoJS.lib.WordArray.random(length);\n return words.toString(CryptoJS.enc.Hex);\n}\n\n// =============================================================================\n// High-Level Functions\n// =============================================================================\n\n/**\n * Generate identity from mnemonic\n * Returns master key derived from mnemonic seed\n */\nexport async function identityFromMnemonic(\n mnemonic: string,\n passphrase: string = ''\n): Promise<MasterKey> {\n if (!validateMnemonic(mnemonic)) {\n throw new SphereError('Invalid mnemonic phrase', 'INVALID_IDENTITY');\n }\n const seedHex = await mnemonicToSeed(mnemonic, passphrase);\n return generateMasterKey(seedHex);\n}\n\n/**\n * Synchronous version of identityFromMnemonic\n */\nexport function identityFromMnemonicSync(\n mnemonic: string,\n passphrase: string = ''\n): MasterKey {\n if (!validateMnemonic(mnemonic)) {\n throw new SphereError('Invalid mnemonic phrase', 'INVALID_IDENTITY');\n }\n const seedHex = mnemonicToSeedSync(mnemonic, passphrase);\n return generateMasterKey(seedHex);\n}\n\n/**\n * Derive address info at a specific path\n * @param masterKey - Master key with privateKey and chainCode\n * @param basePath - Base derivation path (e.g., \"m/44'/0'/0'\")\n * @param index - Address index\n * @param isChange - Whether this is a change address (chain 1 vs 0)\n * @param prefix - Address prefix (default: \"alpha\")\n */\nexport function deriveAddressInfo(\n masterKey: MasterKey,\n basePath: string,\n index: number,\n isChange: boolean = false,\n prefix: string = 'alpha'\n): AddressInfo {\n const chain = isChange ? 1 : 0;\n const fullPath = `${basePath}/${chain}/${index}`;\n\n const derived = deriveKeyAtPath(masterKey.privateKey, masterKey.chainCode, fullPath);\n const publicKey = getPublicKey(derived.privateKey);\n const address = publicKeyToAddress(publicKey, prefix);\n\n return {\n privateKey: derived.privateKey,\n publicKey,\n address,\n path: fullPath,\n index,\n };\n}\n\n/**\n * Generate full address info from private key with index and path\n * (L1 SDK compatibility)\n */\nexport function generateAddressInfo(\n privateKey: string,\n index: number,\n path: string,\n prefix: string = 'alpha'\n): AddressInfo {\n const { address, publicKey } = privateKeyToAddressInfo(privateKey, prefix);\n return {\n privateKey,\n publicKey,\n address,\n path,\n index,\n };\n}\n\n// Re-export elliptic instance for advanced use cases\nexport { ec };\n","/**\n * SDK Error Types\n *\n * Structured error codes for programmatic error handling in UI.\n * UI can switch on error.code to show appropriate user-facing messages.\n *\n * @example\n * ```ts\n * import { SphereError } from '@unicitylabs/sphere-sdk';\n *\n * try {\n * await sphere.payments.send({ ... });\n * } catch (err) {\n * if (err instanceof SphereError) {\n * switch (err.code) {\n * case 'INSUFFICIENT_BALANCE': showToast('Not enough funds'); break;\n * case 'INVALID_RECIPIENT': showToast('Recipient not found'); break;\n * case 'TRANSPORT_ERROR': showToast('Network connection issue'); break;\n * case 'TIMEOUT': showToast('Request timed out, try again'); break;\n * default: showToast(err.message);\n * }\n * }\n * }\n * ```\n */\n\nexport type SphereErrorCode =\n | 'NOT_INITIALIZED'\n | 'ALREADY_INITIALIZED'\n | 'INVALID_CONFIG'\n | 'INVALID_IDENTITY'\n | 'INSUFFICIENT_BALANCE'\n | 'INVALID_RECIPIENT'\n | 'TRANSFER_FAILED'\n | 'STORAGE_ERROR'\n | 'TRANSPORT_ERROR'\n | 'AGGREGATOR_ERROR'\n | 'VALIDATION_ERROR'\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'DECRYPTION_ERROR'\n | 'MODULE_NOT_AVAILABLE';\n\nexport class SphereError extends Error {\n readonly code: SphereErrorCode;\n readonly cause?: unknown;\n\n constructor(message: string, code: SphereErrorCode, cause?: unknown) {\n super(message);\n this.name = 'SphereError';\n this.code = code;\n this.cause = cause;\n }\n}\n\n/**\n * Type guard to check if an error is a SphereError\n */\nexport function isSphereError(err: unknown): err is SphereError {\n return err instanceof SphereError;\n}\n","/**\n * IPNS Key Derivation\n * Deterministic IPNS identity from secp256k1 private key using HKDF\n *\n * Derivation path:\n * secp256k1 privateKey (hex)\n * -> HKDF(sha256, key, info=\"ipfs-storage-ed25519-v1\", 32 bytes)\n * -> Ed25519 key pair\n * -> libp2p PeerId\n * -> IPNS name (e.g., \"12D3KooW...\")\n */\n\nimport { hkdf } from '@noble/hashes/hkdf.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { hexToBytes } from '../../../core/crypto';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/**\n * HKDF info string for deriving Ed25519 keys from wallet keys.\n * Must match sphere webapp for cross-device compatibility.\n */\nexport const IPNS_HKDF_INFO = 'ipfs-storage-ed25519-v1';\n\n// =============================================================================\n// Dynamic Import Cache\n// =============================================================================\n\nlet libp2pCryptoModule: typeof import('@libp2p/crypto/keys') | null = null;\nlet libp2pPeerIdModule: typeof import('@libp2p/peer-id') | null = null;\n\nasync function loadLibp2pModules() {\n if (!libp2pCryptoModule) {\n [libp2pCryptoModule, libp2pPeerIdModule] = await Promise.all([\n import('@libp2p/crypto/keys'),\n import('@libp2p/peer-id'),\n ]);\n }\n return {\n generateKeyPairFromSeed: libp2pCryptoModule!.generateKeyPairFromSeed,\n peerIdFromPrivateKey: libp2pPeerIdModule!.peerIdFromPrivateKey,\n };\n}\n\n// =============================================================================\n// Key Derivation\n// =============================================================================\n\n/**\n * Derive Ed25519 key material from a secp256k1 private key using HKDF.\n *\n * @param privateKeyHex - secp256k1 private key in hex format\n * @param info - HKDF info string (default: IPNS_HKDF_INFO)\n * @returns 32-byte derived key material suitable for Ed25519 seed\n */\nexport function deriveEd25519KeyMaterial(\n privateKeyHex: string,\n info: string = IPNS_HKDF_INFO,\n): Uint8Array {\n const walletSecret = hexToBytes(privateKeyHex);\n const infoBytes = new TextEncoder().encode(info);\n return hkdf(sha256, walletSecret, undefined, infoBytes, 32);\n}\n\n/**\n * Derive full IPNS identity (key pair + IPNS name) from a secp256k1 private key.\n *\n * @param privateKeyHex - secp256k1 private key in hex format\n * @returns Object with keyPair and ipnsName\n */\nexport async function deriveIpnsIdentity(\n privateKeyHex: string,\n): Promise<{ keyPair: unknown; ipnsName: string }> {\n const { generateKeyPairFromSeed, peerIdFromPrivateKey } = await loadLibp2pModules();\n\n const derivedKey = deriveEd25519KeyMaterial(privateKeyHex);\n const keyPair = await generateKeyPairFromSeed('Ed25519', derivedKey);\n const peerId = peerIdFromPrivateKey(keyPair);\n\n return {\n keyPair,\n ipnsName: peerId.toString(),\n };\n}\n\n/**\n * Derive just the IPNS name from a secp256k1 private key.\n * Lighter than deriveIpnsIdentity when you don't need the key pair.\n *\n * @param privateKeyHex - secp256k1 private key in hex format\n * @returns IPNS name string (e.g., \"12D3KooW...\")\n */\nexport async function deriveIpnsName(\n privateKeyHex: string,\n): Promise<string> {\n const { ipnsName } = await deriveIpnsIdentity(privateKeyHex);\n return ipnsName;\n}\n","/**\n * IPNS Record Manager\n * Creates, marshals, and parses IPNS records for publishing and resolution\n */\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Default IPNS record lifetime: 99 years (effectively permanent) */\nconst DEFAULT_LIFETIME_MS = 99 * 365 * 24 * 60 * 60 * 1000;\n\n// =============================================================================\n// Dynamic Import Cache\n// =============================================================================\n\nlet ipnsModule: {\n createIPNSRecord: typeof import('ipns')['createIPNSRecord'];\n marshalIPNSRecord: typeof import('ipns')['marshalIPNSRecord'];\n unmarshalIPNSRecord: typeof import('ipns')['unmarshalIPNSRecord'];\n} | null = null;\n\nasync function loadIpnsModule() {\n if (!ipnsModule) {\n const mod = await import('ipns');\n ipnsModule = {\n createIPNSRecord: mod.createIPNSRecord,\n marshalIPNSRecord: mod.marshalIPNSRecord,\n unmarshalIPNSRecord: mod.unmarshalIPNSRecord,\n };\n }\n return ipnsModule;\n}\n\n// =============================================================================\n// Record Creation\n// =============================================================================\n\n/**\n * Create a signed IPNS record and marshal it to bytes.\n *\n * @param keyPair - Ed25519 private key (from deriveIpnsIdentity)\n * @param cid - CID to point the IPNS record at\n * @param sequenceNumber - Monotonically increasing sequence number\n * @param lifetimeMs - Record validity period (default: 99 years)\n * @returns Marshalled IPNS record bytes\n */\nexport async function createSignedRecord(\n keyPair: unknown,\n cid: string,\n sequenceNumber: bigint,\n lifetimeMs: number = DEFAULT_LIFETIME_MS,\n): Promise<Uint8Array> {\n const { createIPNSRecord, marshalIPNSRecord } = await loadIpnsModule();\n\n const record = await createIPNSRecord(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n keyPair as any,\n `/ipfs/${cid}`,\n sequenceNumber,\n lifetimeMs,\n );\n\n return marshalIPNSRecord(record);\n}\n\n// =============================================================================\n// Record Parsing\n// =============================================================================\n\n/**\n * Parse a routing API response (NDJSON) to extract CID and sequence number.\n * The routing API returns newline-delimited JSON with an \"Extra\" field\n * containing a base64-encoded marshalled IPNS record.\n *\n * @param responseText - Raw text from the routing API response\n * @returns Parsed result with cid, sequence, and recordData, or null\n */\nexport async function parseRoutingApiResponse(\n responseText: string,\n): Promise<{ cid: string; sequence: bigint; recordData: Uint8Array } | null> {\n const { unmarshalIPNSRecord } = await loadIpnsModule();\n\n const lines = responseText.trim().split('\\n');\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const obj = JSON.parse(line);\n\n if (obj.Extra) {\n const recordData = base64ToUint8Array(obj.Extra);\n const record = unmarshalIPNSRecord(recordData);\n\n // Extract CID from the value field\n const valueBytes = typeof record.value === 'string'\n ? new TextEncoder().encode(record.value)\n : record.value as Uint8Array;\n const valueStr = new TextDecoder().decode(valueBytes);\n const cidMatch = valueStr.match(/\\/ipfs\\/([a-zA-Z0-9]+)/);\n\n if (cidMatch) {\n return {\n cid: cidMatch[1],\n sequence: record.sequence,\n recordData,\n };\n }\n }\n } catch {\n // Skip malformed lines\n continue;\n }\n }\n\n return null;\n}\n\n/**\n * Verify that a new sequence number represents a valid progression.\n *\n * @param newSeq - Proposed new sequence number\n * @param lastKnownSeq - Last known sequence number\n * @returns true if the new sequence is valid (greater than last known)\n */\nexport function verifySequenceProgression(\n newSeq: bigint,\n lastKnownSeq: bigint,\n): boolean {\n return newSeq > lastKnownSeq;\n}\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n","/**\n * IPFS Multi-Tier Cache\n * Caches IPNS records, content by CID, and tracks gateway failures\n */\n\nimport type { IpnsGatewayResult } from './ipfs-types';\nimport type { TxfStorageDataBase } from '../../../storage';\n\n// =============================================================================\n// Cache Entry Type\n// =============================================================================\n\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\n// =============================================================================\n// Default Config\n// =============================================================================\n\nconst DEFAULT_IPNS_TTL_MS = 60_000; // 60s for IPNS records\nconst DEFAULT_FAILURE_COOLDOWN_MS = 60_000; // 60s circuit breaker cooldown\nconst DEFAULT_FAILURE_THRESHOLD = 3; // 3 consecutive failures\nconst DEFAULT_KNOWN_FRESH_WINDOW_MS = 30_000; // 30s after publish\n\n// =============================================================================\n// Cache Implementation\n// =============================================================================\n\nexport interface IpfsCacheConfig {\n ipnsTtlMs?: number;\n failureCooldownMs?: number;\n failureThreshold?: number;\n knownFreshWindowMs?: number;\n}\n\nexport class IpfsCache {\n private readonly ipnsRecords = new Map<string, CacheEntry<IpnsGatewayResult>>();\n private readonly content = new Map<string, CacheEntry<TxfStorageDataBase>>();\n private readonly gatewayFailures = new Map<string, { count: number; lastFailure: number }>();\n private readonly knownFreshTimestamps = new Map<string, number>();\n\n private readonly ipnsTtlMs: number;\n private readonly failureCooldownMs: number;\n private readonly failureThreshold: number;\n private readonly knownFreshWindowMs: number;\n\n constructor(config?: IpfsCacheConfig) {\n this.ipnsTtlMs = config?.ipnsTtlMs ?? DEFAULT_IPNS_TTL_MS;\n this.failureCooldownMs = config?.failureCooldownMs ?? DEFAULT_FAILURE_COOLDOWN_MS;\n this.failureThreshold = config?.failureThreshold ?? DEFAULT_FAILURE_THRESHOLD;\n this.knownFreshWindowMs = config?.knownFreshWindowMs ?? DEFAULT_KNOWN_FRESH_WINDOW_MS;\n }\n\n // ---------------------------------------------------------------------------\n // IPNS Record Cache (60s TTL)\n // ---------------------------------------------------------------------------\n\n getIpnsRecord(ipnsName: string): IpnsGatewayResult | null {\n const entry = this.ipnsRecords.get(ipnsName);\n if (!entry) return null;\n\n if (Date.now() - entry.timestamp > this.ipnsTtlMs) {\n this.ipnsRecords.delete(ipnsName);\n return null;\n }\n\n return entry.data;\n }\n\n /**\n * Get cached IPNS record ignoring TTL (for known-fresh optimization).\n */\n getIpnsRecordIgnoreTtl(ipnsName: string): IpnsGatewayResult | null {\n const entry = this.ipnsRecords.get(ipnsName);\n return entry?.data ?? null;\n }\n\n setIpnsRecord(ipnsName: string, result: IpnsGatewayResult): void {\n this.ipnsRecords.set(ipnsName, {\n data: result,\n timestamp: Date.now(),\n });\n }\n\n invalidateIpns(ipnsName: string): void {\n this.ipnsRecords.delete(ipnsName);\n }\n\n // ---------------------------------------------------------------------------\n // Content Cache (infinite TTL - content is immutable by CID)\n // ---------------------------------------------------------------------------\n\n getContent(cid: string): TxfStorageDataBase | null {\n const entry = this.content.get(cid);\n return entry?.data ?? null;\n }\n\n setContent(cid: string, data: TxfStorageDataBase): void {\n this.content.set(cid, {\n data,\n timestamp: Date.now(),\n });\n }\n\n // ---------------------------------------------------------------------------\n // Gateway Failure Tracking (Circuit Breaker)\n // ---------------------------------------------------------------------------\n\n /**\n * Record a gateway failure. After threshold consecutive failures,\n * the gateway enters cooldown and is considered unhealthy.\n */\n recordGatewayFailure(gateway: string): void {\n const existing = this.gatewayFailures.get(gateway);\n this.gatewayFailures.set(gateway, {\n count: (existing?.count ?? 0) + 1,\n lastFailure: Date.now(),\n });\n }\n\n /** Reset failure count for a gateway (on successful request) */\n recordGatewaySuccess(gateway: string): void {\n this.gatewayFailures.delete(gateway);\n }\n\n /**\n * Check if a gateway is currently in circuit breaker cooldown.\n * A gateway is considered unhealthy if it has had >= threshold\n * consecutive failures and the cooldown period hasn't elapsed.\n */\n isGatewayInCooldown(gateway: string): boolean {\n const failure = this.gatewayFailures.get(gateway);\n if (!failure) return false;\n\n if (failure.count < this.failureThreshold) return false;\n\n const elapsed = Date.now() - failure.lastFailure;\n if (elapsed >= this.failureCooldownMs) {\n // Cooldown expired, reset\n this.gatewayFailures.delete(gateway);\n return false;\n }\n\n return true;\n }\n\n // ---------------------------------------------------------------------------\n // Known-Fresh Flag (FAST mode optimization)\n // ---------------------------------------------------------------------------\n\n /**\n * Mark IPNS cache as \"known-fresh\" (after local publish or push notification).\n * Within the fresh window, we can skip network resolution.\n */\n markIpnsFresh(ipnsName: string): void {\n this.knownFreshTimestamps.set(ipnsName, Date.now());\n }\n\n /**\n * Check if the cache is known-fresh (within the fresh window).\n */\n isIpnsKnownFresh(ipnsName: string): boolean {\n const timestamp = this.knownFreshTimestamps.get(ipnsName);\n if (!timestamp) return false;\n\n if (Date.now() - timestamp > this.knownFreshWindowMs) {\n this.knownFreshTimestamps.delete(ipnsName);\n return false;\n }\n\n return true;\n }\n\n // ---------------------------------------------------------------------------\n // Cache Management\n // ---------------------------------------------------------------------------\n\n clear(): void {\n this.ipnsRecords.clear();\n this.content.clear();\n this.gatewayFailures.clear();\n this.knownFreshTimestamps.clear();\n }\n}\n","/**\n * Centralized SDK Logger\n *\n * A lightweight singleton logger that works across all tsup bundles\n * by storing state on globalThis. Supports three log levels:\n * - debug: detailed messages (only shown when debug=true)\n * - warn: important warnings (ALWAYS shown regardless of debug flag)\n * - error: critical errors (ALWAYS shown regardless of debug flag)\n *\n * Global debug flag enables all logging. Per-tag overrides allow\n * granular control (e.g., only transport debug).\n *\n * @example\n * ```ts\n * import { logger } from '@unicitylabs/sphere-sdk';\n *\n * // Enable all debug logging\n * logger.configure({ debug: true });\n *\n * // Enable only specific tags\n * logger.setTagDebug('Nostr', true);\n *\n * // Usage in SDK classes\n * logger.debug('Payments', 'Transfer started', { amount, recipient });\n * logger.warn('Nostr', 'queryEvents timed out after 5s');\n * logger.error('Sphere', 'Critical failure', error);\n * ```\n */\n\nexport type LogLevel = 'debug' | 'warn' | 'error';\n\nexport type LogHandler = (level: LogLevel, tag: string, message: string, ...args: unknown[]) => void;\n\nexport interface LoggerConfig {\n /** Enable debug logging globally (default: false). When false, only warn and error messages are shown. */\n debug?: boolean;\n /** Custom log handler. If provided, replaces console output. Useful for tests or custom log sinks. */\n handler?: LogHandler | null;\n}\n\n// Use a unique symbol-like key on globalThis to share logger state across tsup bundles\nconst LOGGER_KEY = '__sphere_sdk_logger__';\n\ninterface LoggerState {\n debug: boolean;\n tags: Record<string, boolean>;\n handler: LogHandler | null;\n}\n\nfunction getState(): LoggerState {\n const g = globalThis as unknown as Record<string, unknown>;\n if (!g[LOGGER_KEY]) {\n g[LOGGER_KEY] = { debug: false, tags: {}, handler: null } satisfies LoggerState;\n }\n return g[LOGGER_KEY] as LoggerState;\n}\n\nfunction isEnabled(tag: string): boolean {\n const state = getState();\n // Per-tag override takes priority\n if (tag in state.tags) return state.tags[tag];\n // Fall back to global flag\n return state.debug;\n}\n\nexport const logger = {\n /**\n * Configure the logger. Can be called multiple times (last write wins).\n * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().\n */\n configure(config: LoggerConfig): void {\n const state = getState();\n if (config.debug !== undefined) state.debug = config.debug;\n if (config.handler !== undefined) state.handler = config.handler;\n },\n\n /**\n * Enable/disable debug logging for a specific tag.\n * Per-tag setting overrides the global debug flag.\n *\n * @example\n * ```ts\n * logger.setTagDebug('Nostr', true); // enable only Nostr logs\n * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true\n * ```\n */\n setTagDebug(tag: string, enabled: boolean): void {\n getState().tags[tag] = enabled;\n },\n\n /**\n * Clear per-tag override, falling back to global debug flag.\n */\n clearTagDebug(tag: string): void {\n delete getState().tags[tag];\n },\n\n /** Returns true if debug mode is enabled for the given tag (or globally). */\n isDebugEnabled(tag?: string): boolean {\n if (tag) return isEnabled(tag);\n return getState().debug;\n },\n\n /**\n * Debug-level log. Only shown when debug is enabled (globally or for this tag).\n * Use for detailed operational information.\n */\n debug(tag: string, message: string, ...args: unknown[]): void {\n if (!isEnabled(tag)) return;\n const state = getState();\n if (state.handler) {\n state.handler('debug', tag, message, ...args);\n } else {\n console.log(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Warning-level log. ALWAYS shown regardless of debug flag.\n * Use for important but non-critical issues (timeouts, retries, degraded state).\n */\n warn(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('warn', tag, message, ...args);\n } else {\n console.warn(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Error-level log. ALWAYS shown regardless of debug flag.\n * Use for critical failures that should never be silenced.\n */\n error(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('error', tag, message, ...args);\n } else {\n console.error(`[${tag}]`, message, ...args);\n }\n },\n\n /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */\n reset(): void {\n const g = globalThis as unknown as Record<string, unknown>;\n delete g[LOGGER_KEY];\n },\n};\n","/**\n * IPFS HTTP Client\n * Fetch-based HTTP API client for IPFS gateway operations.\n * Works in both browser and Node.js (native fetch available since Node 18+).\n */\n\nimport { logger } from '../../../core/logger';\nimport type {\n IpnsGatewayResult,\n IpnsProgressiveResult,\n IpnsPublishResult,\n GatewayHealthResult,\n} from './ipfs-types';\nimport type { TxfStorageDataBase } from '../../../storage';\nimport {\n IpfsError,\n classifyFetchError,\n classifyHttpStatus,\n} from './ipfs-error-types';\nimport { IpfsCache } from './ipfs-cache';\nimport { parseRoutingApiResponse } from './ipns-record-manager';\n\n// =============================================================================\n// Default Timeouts\n// =============================================================================\n\nconst DEFAULT_CONNECTIVITY_TIMEOUT_MS = 5000;\nconst DEFAULT_FETCH_TIMEOUT_MS = 15000;\nconst DEFAULT_RESOLVE_TIMEOUT_MS = 10000;\nconst DEFAULT_PUBLISH_TIMEOUT_MS = 30000;\nconst DEFAULT_GATEWAY_PATH_TIMEOUT_MS = 3000;\nconst DEFAULT_ROUTING_API_TIMEOUT_MS = 2000;\n\n// =============================================================================\n// HTTP Client\n// =============================================================================\n\nexport interface IpfsHttpClientConfig {\n gateways: string[];\n fetchTimeoutMs?: number;\n resolveTimeoutMs?: number;\n publishTimeoutMs?: number;\n connectivityTimeoutMs?: number;\n debug?: boolean;\n}\n\nexport class IpfsHttpClient {\n private readonly gateways: string[];\n private readonly fetchTimeoutMs: number;\n private readonly resolveTimeoutMs: number;\n private readonly publishTimeoutMs: number;\n private readonly connectivityTimeoutMs: number;\n private readonly debug: boolean;\n private readonly cache: IpfsCache;\n\n constructor(config: IpfsHttpClientConfig, cache: IpfsCache) {\n this.gateways = config.gateways;\n this.fetchTimeoutMs = config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;\n this.resolveTimeoutMs = config.resolveTimeoutMs ?? DEFAULT_RESOLVE_TIMEOUT_MS;\n this.publishTimeoutMs = config.publishTimeoutMs ?? DEFAULT_PUBLISH_TIMEOUT_MS;\n this.connectivityTimeoutMs = config.connectivityTimeoutMs ?? DEFAULT_CONNECTIVITY_TIMEOUT_MS;\n this.debug = config.debug ?? false;\n this.cache = cache;\n }\n\n // ---------------------------------------------------------------------------\n // Public Accessors\n // ---------------------------------------------------------------------------\n\n /**\n * Get configured gateway URLs.\n */\n getGateways(): string[] {\n return [...this.gateways];\n }\n\n // ---------------------------------------------------------------------------\n // Gateway Health\n // ---------------------------------------------------------------------------\n\n /**\n * Test connectivity to a single gateway.\n */\n async testConnectivity(gateway: string): Promise<GatewayHealthResult> {\n const start = Date.now();\n try {\n const response = await this.fetchWithTimeout(\n `${gateway}/api/v0/version`,\n this.connectivityTimeoutMs,\n { method: 'POST' },\n );\n\n if (!response.ok) {\n return { gateway, healthy: false, error: `HTTP ${response.status}` };\n }\n\n return {\n gateway,\n healthy: true,\n responseTimeMs: Date.now() - start,\n };\n } catch (error) {\n return {\n gateway,\n healthy: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n /**\n * Find healthy gateways from the configured list.\n */\n async findHealthyGateways(): Promise<string[]> {\n const results = await Promise.allSettled(\n this.gateways.map((gw) => this.testConnectivity(gw)),\n );\n\n return results\n .filter((r): r is PromiseFulfilledResult<GatewayHealthResult> =>\n r.status === 'fulfilled' && r.value.healthy)\n .map((r) => r.value.gateway);\n }\n\n /**\n * Get gateways that are not in circuit breaker cooldown.\n */\n getAvailableGateways(): string[] {\n return this.gateways.filter((gw) => !this.cache.isGatewayInCooldown(gw));\n }\n\n // ---------------------------------------------------------------------------\n // Content Upload\n // ---------------------------------------------------------------------------\n\n /**\n * Upload JSON content to IPFS.\n * Tries all gateways in parallel, returns first success.\n */\n async upload(data: unknown, gateways?: string[]): Promise<{ cid: string }> {\n const targets = gateways ?? this.getAvailableGateways();\n if (targets.length === 0) {\n throw new IpfsError('No gateways available for upload', 'NETWORK_ERROR');\n }\n\n const jsonBytes = new TextEncoder().encode(JSON.stringify(data));\n\n const promises = targets.map(async (gateway) => {\n try {\n const formData = new FormData();\n formData.append('file', new Blob([jsonBytes], { type: 'application/json' }), 'data.json');\n\n const response = await this.fetchWithTimeout(\n `${gateway}/api/v0/add?pin=true&cid-version=1`,\n this.publishTimeoutMs,\n { method: 'POST', body: formData },\n );\n\n if (!response.ok) {\n throw new IpfsError(\n `Upload failed: HTTP ${response.status}`,\n classifyHttpStatus(response.status),\n gateway,\n );\n }\n\n const result = await response.json();\n this.cache.recordGatewaySuccess(gateway);\n this.log(`Uploaded to ${gateway}: CID=${result.Hash}`);\n return { cid: result.Hash as string, gateway };\n } catch (error) {\n if (error instanceof IpfsError && error.shouldTriggerCircuitBreaker) {\n this.cache.recordGatewayFailure(gateway);\n }\n throw error;\n }\n });\n\n try {\n const result = await Promise.any(promises);\n return { cid: result.cid };\n } catch (error) {\n if (error instanceof AggregateError) {\n throw new IpfsError(\n `Upload failed on all gateways: ${error.errors.map(e => e.message).join('; ')}`,\n 'NETWORK_ERROR',\n );\n }\n throw error;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Content Fetch\n // ---------------------------------------------------------------------------\n\n /**\n * Fetch content by CID from IPFS gateways.\n * Checks content cache first. Races all gateways for fastest response.\n */\n async fetchContent<T extends TxfStorageDataBase>(\n cid: string,\n gateways?: string[],\n ): Promise<T> {\n // Check content cache (infinite TTL for immutable content)\n const cached = this.cache.getContent(cid);\n if (cached) {\n this.log(`Content cache hit for CID=${cid}`);\n return cached as T;\n }\n\n const targets = gateways ?? this.getAvailableGateways();\n if (targets.length === 0) {\n throw new IpfsError('No gateways available for fetch', 'NETWORK_ERROR');\n }\n\n const promises = targets.map(async (gateway) => {\n try {\n const response = await this.fetchWithTimeout(\n `${gateway}/ipfs/${cid}`,\n this.fetchTimeoutMs,\n { headers: { Accept: 'application/octet-stream' } },\n );\n\n if (!response.ok) {\n const body = await response.text().catch((err) => { logger.debug('IPFS-HTTP', 'Failed to read error response body', err); return ''; });\n throw new IpfsError(\n `Fetch failed: HTTP ${response.status}`,\n classifyHttpStatus(response.status, body),\n gateway,\n );\n }\n\n const data = await response.json() as T;\n this.cache.recordGatewaySuccess(gateway);\n this.cache.setContent(cid, data);\n this.log(`Fetched from ${gateway}: CID=${cid}`);\n return data;\n } catch (error) {\n if (error instanceof IpfsError && error.shouldTriggerCircuitBreaker) {\n this.cache.recordGatewayFailure(gateway);\n }\n throw error;\n }\n });\n\n try {\n return await Promise.any(promises);\n } catch (error) {\n if (error instanceof AggregateError) {\n throw new IpfsError(\n `Fetch failed on all gateways for CID=${cid}`,\n 'NETWORK_ERROR',\n );\n }\n throw error;\n }\n }\n\n // ---------------------------------------------------------------------------\n // IPNS Resolution\n // ---------------------------------------------------------------------------\n\n /**\n * Resolve IPNS via Routing API (returns record with sequence number).\n * POST /api/v0/routing/get?arg=/ipns/{name}\n */\n async resolveIpnsViaRoutingApi(\n gateway: string,\n ipnsName: string,\n timeoutMs: number = DEFAULT_ROUTING_API_TIMEOUT_MS,\n ): Promise<IpnsGatewayResult | null> {\n try {\n const response = await this.fetchWithTimeout(\n `${gateway}/api/v0/routing/get?arg=/ipns/${ipnsName}`,\n timeoutMs,\n { method: 'POST' },\n );\n\n if (!response.ok) {\n const body = await response.text().catch((err) => { logger.debug('IPFS-HTTP', 'Failed to read error response body', err); return ''; });\n const category = classifyHttpStatus(response.status, body);\n if (category === 'NOT_FOUND') return null;\n throw new IpfsError(`Routing API: HTTP ${response.status}`, category, gateway);\n }\n\n const text = await response.text();\n const parsed = await parseRoutingApiResponse(text);\n\n if (!parsed) return null;\n\n this.cache.recordGatewaySuccess(gateway);\n return {\n cid: parsed.cid,\n sequence: parsed.sequence,\n gateway,\n recordData: parsed.recordData,\n };\n } catch (error) {\n if (error instanceof IpfsError) throw error;\n const category = classifyFetchError(error);\n if (category !== 'NOT_FOUND') {\n this.cache.recordGatewayFailure(gateway);\n }\n return null;\n }\n }\n\n /**\n * Resolve IPNS via gateway path (simpler, no sequence number).\n * GET /ipns/{name}?format=dag-json\n */\n async resolveIpnsViaGatewayPath(\n gateway: string,\n ipnsName: string,\n timeoutMs: number = DEFAULT_GATEWAY_PATH_TIMEOUT_MS,\n ): Promise<{ cid: string; content?: unknown } | null> {\n try {\n const response = await this.fetchWithTimeout(\n `${gateway}/ipns/${ipnsName}`,\n timeoutMs,\n { headers: { Accept: 'application/json' } },\n );\n\n if (!response.ok) return null;\n\n const content = await response.json();\n const cidHeader = response.headers.get('X-Ipfs-Path');\n if (cidHeader) {\n const match = cidHeader.match(/\\/ipfs\\/([a-zA-Z0-9]+)/);\n if (match) {\n this.cache.recordGatewaySuccess(gateway);\n return { cid: match[1], content };\n }\n }\n\n return { cid: '', content };\n } catch (err) {\n logger.debug('IPFS-HTTP', 'IPNS gateway resolution failed', err);\n return null;\n }\n }\n\n /**\n * Progressive IPNS resolution across all gateways.\n * Queries all gateways in parallel, returns highest sequence number.\n */\n async resolveIpns(\n ipnsName: string,\n gateways?: string[],\n ): Promise<IpnsProgressiveResult> {\n const targets = gateways ?? this.getAvailableGateways();\n if (targets.length === 0) {\n return { best: null, allResults: [], respondedCount: 0, totalGateways: 0 };\n }\n\n const results: IpnsGatewayResult[] = [];\n let respondedCount = 0;\n\n const promises = targets.map(async (gateway) => {\n const result = await this.resolveIpnsViaRoutingApi(\n gateway,\n ipnsName,\n this.resolveTimeoutMs,\n );\n if (result) results.push(result);\n respondedCount++;\n return result;\n });\n\n // Wait for all to complete (with overall timeout)\n await Promise.race([\n Promise.allSettled(promises),\n new Promise<void>((resolve) =>\n setTimeout(resolve, this.resolveTimeoutMs + 1000)),\n ]);\n\n // Find best result (highest sequence)\n let best: IpnsGatewayResult | null = null;\n for (const result of results) {\n if (!best || result.sequence > best.sequence) {\n best = result;\n }\n }\n\n if (best) {\n this.cache.setIpnsRecord(ipnsName, best);\n }\n\n return {\n best,\n allResults: results,\n respondedCount,\n totalGateways: targets.length,\n };\n }\n\n // ---------------------------------------------------------------------------\n // IPNS Publishing\n // ---------------------------------------------------------------------------\n\n /**\n * Publish IPNS record to a single gateway via routing API.\n */\n async publishIpnsViaRoutingApi(\n gateway: string,\n ipnsName: string,\n marshalledRecord: Uint8Array,\n timeoutMs: number = DEFAULT_PUBLISH_TIMEOUT_MS,\n ): Promise<boolean> {\n try {\n const formData = new FormData();\n formData.append(\n 'file',\n new Blob([new Uint8Array(marshalledRecord)]),\n 'record',\n );\n\n const response = await this.fetchWithTimeout(\n `${gateway}/api/v0/routing/put?arg=/ipns/${ipnsName}&allow-offline=true`,\n timeoutMs,\n { method: 'POST', body: formData },\n );\n\n if (!response.ok) {\n const errorText = await response.text().catch((err) => { logger.debug('IPFS-HTTP', 'Failed to read error response body', err); return ''; });\n throw new IpfsError(\n `IPNS publish: HTTP ${response.status}: ${errorText.slice(0, 100)}`,\n classifyHttpStatus(response.status, errorText),\n gateway,\n );\n }\n\n this.cache.recordGatewaySuccess(gateway);\n this.log(`IPNS published to ${gateway}: ${ipnsName}`);\n return true;\n } catch (error) {\n if (error instanceof IpfsError && error.shouldTriggerCircuitBreaker) {\n this.cache.recordGatewayFailure(gateway);\n }\n this.log(`IPNS publish to ${gateway} failed: ${error}`);\n return false;\n }\n }\n\n /**\n * Publish IPNS record to all gateways in parallel.\n */\n async publishIpns(\n ipnsName: string,\n marshalledRecord: Uint8Array,\n gateways?: string[],\n ): Promise<IpnsPublishResult> {\n const targets = gateways ?? this.getAvailableGateways();\n if (targets.length === 0) {\n return { success: false, error: 'No gateways available' };\n }\n\n const results = await Promise.allSettled(\n targets.map((gw) =>\n this.publishIpnsViaRoutingApi(gw, ipnsName, marshalledRecord, this.publishTimeoutMs)),\n );\n\n const successfulGateways: string[] = [];\n results.forEach((result, index) => {\n if (result.status === 'fulfilled' && result.value) {\n successfulGateways.push(targets[index]);\n }\n });\n\n return {\n success: successfulGateways.length > 0,\n ipnsName,\n successfulGateways,\n error: successfulGateways.length === 0 ? 'All gateways failed' : undefined,\n };\n }\n\n // ---------------------------------------------------------------------------\n // IPNS Verification\n // ---------------------------------------------------------------------------\n\n /**\n * Verify IPNS record persistence after publishing.\n * Retries resolution to confirm the record was accepted.\n */\n async verifyIpnsRecord(\n ipnsName: string,\n expectedSeq: bigint,\n expectedCid: string,\n retries: number = 3,\n delayMs: number = 1000,\n ): Promise<boolean> {\n for (let i = 0; i < retries; i++) {\n if (i > 0) {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n const { best } = await this.resolveIpns(ipnsName);\n if (best && best.sequence >= expectedSeq && best.cid === expectedCid) {\n return true;\n }\n }\n return false;\n }\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n private async fetchWithTimeout(\n url: string,\n timeoutMs: number,\n options?: RequestInit,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n return await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n private log(message: string): void {\n logger.debug('IPFS-HTTP', message);\n }\n}\n","/**\n * TXF Data Merge / Conflict Resolution\n * Merges local and remote TXF storage data with proper conflict handling\n */\n\nimport type { TxfStorageDataBase, TxfTombstone } from '../../../storage';\nimport type { MergeResult } from './ipfs-types';\n\n// =============================================================================\n// Merge Logic\n// =============================================================================\n\n/**\n * Merge local and remote TXF data.\n *\n * Rules:\n * 1. Meta: Higher version wins as base; increment by 1\n * 2. Tombstones: Union by composite key (tokenId, stateHash)\n * 3. Token entries: present in only one source -> add; both -> local wins\n * 4. Tombstone filtering: exclude tokens present in merged tombstones\n * 5. Outbox/Sent: Union with dedup by id/tokenId\n */\nexport function mergeTxfData<T extends TxfStorageDataBase>(\n local: T,\n remote: T,\n): MergeResult<T> {\n let added = 0;\n let removed = 0;\n let conflicts = 0;\n\n // 1. Merge meta — use higher version as base, increment\n const localVersion = local._meta?.version ?? 0;\n const remoteVersion = remote._meta?.version ?? 0;\n const baseMeta = localVersion >= remoteVersion ? local._meta : remote._meta;\n const mergedMeta = {\n ...baseMeta,\n version: Math.max(localVersion, remoteVersion) + 1,\n updatedAt: Date.now(),\n };\n\n // 2. Merge tombstones — union by composite key (tokenId + stateHash)\n const mergedTombstones = mergeTombstones(\n local._tombstones ?? [],\n remote._tombstones ?? [],\n );\n const tombstoneKeys = new Set(\n mergedTombstones.map((t) => `${t.tokenId}:${t.stateHash}`),\n );\n\n // 3. Merge token entries\n const localTokenKeys = getTokenKeys(local);\n const remoteTokenKeys = getTokenKeys(remote);\n const allTokenKeys = new Set([...localTokenKeys, ...remoteTokenKeys]);\n\n const mergedTokens: Record<string, unknown> = {};\n\n for (const key of allTokenKeys) {\n const tokenId = key.startsWith('_') ? key.slice(1) : key;\n const localToken = (local as Record<string, unknown>)[key];\n const remoteToken = (remote as Record<string, unknown>)[key];\n\n // Check tombstone filter\n if (isTokenTombstoned(tokenId, localToken, remoteToken, tombstoneKeys)) {\n if (localTokenKeys.has(key)) removed++;\n continue;\n }\n\n if (localToken && !remoteToken) {\n // Only in local\n mergedTokens[key] = localToken;\n } else if (!localToken && remoteToken) {\n // Only in remote\n mergedTokens[key] = remoteToken;\n added++;\n } else if (localToken && remoteToken) {\n // In both — local wins (with conflict count)\n mergedTokens[key] = localToken;\n conflicts++;\n }\n }\n\n // 4. Merge outbox — union with dedup by id\n const mergedOutbox = mergeArrayById(\n local._outbox ?? [],\n remote._outbox ?? [],\n 'id',\n );\n\n // 5. Merge sent — union with dedup by tokenId\n const mergedSent = mergeArrayById(\n local._sent ?? [],\n remote._sent ?? [],\n 'tokenId',\n );\n\n // 6. Merge invalid — union with dedup by tokenId\n const mergedInvalid = mergeArrayById(\n local._invalid ?? [],\n remote._invalid ?? [],\n 'tokenId',\n );\n\n // 7. Merge nametags — union by name, local wins on conflict\n type NametagEntry = { name: string; [key: string]: unknown };\n const localNametags = (local._nametags ?? []) as NametagEntry[];\n const remoteNametags = (remote._nametags ?? []) as NametagEntry[];\n const mergedNametags = mergeNametagsByName(localNametags, remoteNametags);\n\n // 8. Merge history — union with dedup by dedupKey (same pattern as _outbox/_sent/_invalid)\n const localHistory = (local._history ?? []) as Array<{ dedupKey: string }>;\n const remoteHistory = (remote._history ?? []) as Array<{ dedupKey: string }>;\n const mergedHistory = mergeArrayById(localHistory, remoteHistory, 'dedupKey');\n\n // Build merged result\n const merged = {\n _meta: mergedMeta,\n _tombstones: mergedTombstones.length > 0 ? mergedTombstones : undefined,\n _nametags: mergedNametags.length > 0 ? mergedNametags : undefined,\n _outbox: mergedOutbox.length > 0 ? mergedOutbox : undefined,\n _sent: mergedSent.length > 0 ? mergedSent : undefined,\n _invalid: mergedInvalid.length > 0 ? mergedInvalid : undefined,\n _history: mergedHistory.length > 0 ? mergedHistory : undefined,\n ...mergedTokens,\n } as unknown as T;\n\n return { merged, added, removed, conflicts };\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/**\n * Merge tombstone arrays by composite key (tokenId + stateHash).\n * On duplicates, keep the one with the newer timestamp.\n */\nfunction mergeTombstones(\n local: TxfTombstone[],\n remote: TxfTombstone[],\n): TxfTombstone[] {\n const merged = new Map<string, TxfTombstone>();\n\n for (const tombstone of [...local, ...remote]) {\n const key = `${tombstone.tokenId}:${tombstone.stateHash}`;\n const existing = merged.get(key);\n if (!existing || tombstone.timestamp > existing.timestamp) {\n merged.set(key, tombstone);\n }\n }\n\n return Array.from(merged.values());\n}\n\n/**\n * Get all token entry keys from TXF data.\n * Token keys start with '_' but are not meta fields.\n */\nfunction getTokenKeys(data: TxfStorageDataBase): Set<string> {\n const reservedKeys = new Set([\n '_meta', '_tombstones', '_outbox', '_sent', '_invalid',\n '_nametag', '_nametags', '_mintOutbox', '_invalidatedNametags', '_integrity',\n '_history',\n ]);\n const keys = new Set<string>();\n\n for (const key of Object.keys(data)) {\n if (reservedKeys.has(key)) continue;\n // Exclude archived and forked entries from token merge\n if (key.startsWith('archived-') || key.startsWith('_forked_')) continue;\n keys.add(key);\n }\n\n return keys;\n}\n\n/**\n * Check if a token should be filtered by tombstones.\n */\nfunction isTokenTombstoned(\n tokenId: string,\n localToken: unknown,\n remoteToken: unknown,\n tombstoneKeys: Set<string>,\n): boolean {\n // Check if any variant of this token is tombstoned\n // We check generic tokenId matching since we may not have the stateHash here\n for (const key of tombstoneKeys) {\n if (key.startsWith(`${tokenId}:`)) {\n return true;\n }\n }\n // Keep token if not tombstoned\n void localToken;\n void remoteToken;\n return false;\n}\n\n/**\n * Merge nametag arrays by name, deduplicating.\n * On duplicates, keep the local entry.\n */\nfunction mergeNametagsByName(\n local: Array<{ name: string; [key: string]: unknown }>,\n remote: Array<{ name: string; [key: string]: unknown }>,\n): Array<{ name: string; [key: string]: unknown }> {\n const seen = new Map<string, { name: string; [key: string]: unknown }>();\n\n for (const item of local) {\n if (item.name) seen.set(item.name, item);\n }\n for (const item of remote) {\n if (item.name && !seen.has(item.name)) {\n seen.set(item.name, item);\n }\n }\n\n return Array.from(seen.values());\n}\n\n/**\n * Merge arrays by a key field, deduplicating.\n * On duplicates, keep the one from the first array (local).\n */\nfunction mergeArrayById<T>(\n local: T[],\n remote: T[],\n idField: keyof T,\n): T[] {\n const seen = new Map<unknown, T>();\n\n for (const item of local) {\n const id = item[idField];\n if (id !== undefined) {\n seen.set(id, item);\n }\n }\n\n for (const item of remote) {\n const id = item[idField];\n if (id !== undefined && !seen.has(id)) {\n seen.set(id, item);\n }\n }\n\n return Array.from(seen.values());\n}\n","/**\n * WebSocket Abstraction\n * Platform-independent WebSocket interface for cross-platform support\n */\n\n// =============================================================================\n// WebSocket Interface\n// =============================================================================\n\n/**\n * Minimal WebSocket interface compatible with browser and Node.js\n */\nexport interface IWebSocket {\n readonly readyState: number;\n\n send(data: string): void;\n close(code?: number, reason?: string): void;\n\n onopen: ((event: unknown) => void) | null;\n onclose: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n onmessage: ((event: IMessageEvent) => void) | null;\n}\n\nexport interface IMessageEvent {\n data: string;\n}\n\n/**\n * WebSocket ready states (same as native WebSocket)\n */\nexport const WebSocketReadyState = {\n CONNECTING: 0,\n OPEN: 1,\n CLOSING: 2,\n CLOSED: 3,\n} as const;\n\n/**\n * Factory function to create WebSocket instances\n * Different implementations for browser (native) vs Node.js (ws package)\n */\nexport type WebSocketFactory = (url: string) => IWebSocket;\n\n// =============================================================================\n// UUID Generator\n// =============================================================================\n\n/**\n * Generate a unique ID (platform-independent)\n * Browser: crypto.randomUUID()\n * Node: crypto.randomUUID() or uuid package\n */\nexport type UUIDGenerator = () => string;\n\n/**\n * Default UUID generator using crypto.randomUUID\n * Works in modern browsers and Node 19+\n */\nexport function defaultUUIDGenerator(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older environments\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n","/**\n * WebSocket client for IPNS subscription updates.\n * Connects to IPFS sidecar and receives push notifications for IPNS changes.\n *\n * Architecture:\n * - Connects to /ws/ipns endpoint on IPFS gateway\n * - Clients subscribe to specific IPNS names\n * - Server pushes updates when IPNS records change\n * - Auto-reconnects on connection loss with exponential backoff\n * - Falls back to polling when WebSocket is unavailable\n *\n * Ported from Sphere webapp's IpnsSubscriptionClient.\n */\n\nimport { logger } from '../../../core/logger';\nimport type { IWebSocket, WebSocketFactory } from '../../../transport/websocket';\nimport { WebSocketReadyState } from '../../../transport/websocket';\nimport type { IpnsUpdateEvent } from './ipfs-types';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface IpnsSubscriptionClientConfig {\n /** WebSocket URL (e.g., wss://host/ws/ipns) */\n wsUrl: string;\n /** Platform-specific WebSocket factory */\n createWebSocket: WebSocketFactory;\n /** Ping interval in ms (default: 30000) */\n pingIntervalMs?: number;\n /** Initial reconnect delay in ms (default: 5000) */\n reconnectDelayMs?: number;\n /** Max reconnect delay in ms (default: 60000) */\n maxReconnectDelayMs?: number;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\ntype UpdateCallback = (update: IpnsUpdateEvent) => void;\n\ninterface WebSocketMessage {\n type: string;\n name?: string;\n names?: string[];\n sequence?: number;\n cid?: string | null;\n timestamp?: string;\n message?: string;\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\nexport class IpnsSubscriptionClient {\n private ws: IWebSocket | null = null;\n private readonly subscriptions: Map<string, Set<UpdateCallback>> = new Map();\n private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n private pingInterval: ReturnType<typeof setInterval> | null = null;\n private fallbackPollInterval: ReturnType<typeof setInterval> | null = null;\n\n private readonly wsUrl: string;\n private readonly createWebSocket: WebSocketFactory;\n private readonly pingIntervalMs: number;\n private readonly initialReconnectDelayMs: number;\n private readonly maxReconnectDelayMs: number;\n private readonly debugEnabled: boolean;\n\n private reconnectAttempts = 0;\n private isConnecting = false;\n private connectionOpenedAt = 0;\n private destroyed = false;\n\n /** Minimum stable connection time before resetting backoff (30 seconds) */\n private readonly minStableConnectionMs = 30000;\n\n private fallbackPollFn: (() => Promise<void>) | null = null;\n private fallbackPollIntervalMs = 0;\n\n constructor(config: IpnsSubscriptionClientConfig) {\n this.wsUrl = config.wsUrl;\n this.createWebSocket = config.createWebSocket;\n this.pingIntervalMs = config.pingIntervalMs ?? 30000;\n this.initialReconnectDelayMs = config.reconnectDelayMs ?? 5000;\n this.maxReconnectDelayMs = config.maxReconnectDelayMs ?? 60000;\n this.debugEnabled = config.debug ?? false;\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /**\n * Subscribe to IPNS updates for a specific name.\n * Automatically connects the WebSocket if not already connected.\n * If WebSocket is connecting, the name will be subscribed once connected.\n */\n subscribe(ipnsName: string, callback: UpdateCallback): () => void {\n if (!ipnsName || typeof ipnsName !== 'string') {\n this.log('Invalid IPNS name for subscription');\n return () => { /* noop */ };\n }\n\n const isNewSubscription = !this.subscriptions.has(ipnsName);\n\n if (isNewSubscription) {\n this.subscriptions.set(ipnsName, new Set());\n }\n\n this.subscriptions.get(ipnsName)!.add(callback);\n\n // Send subscription to server if already connected and this is a new name\n if (isNewSubscription && this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.sendSubscribe([ipnsName]);\n }\n\n // Connect if not already connected\n if (!this.ws || this.ws.readyState !== WebSocketReadyState.OPEN) {\n this.connect();\n }\n\n // Return unsubscribe function\n return () => {\n const callbacks = this.subscriptions.get(ipnsName);\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n this.subscriptions.delete(ipnsName);\n if (this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.sendUnsubscribe([ipnsName]);\n }\n // Disconnect if no more subscriptions\n if (this.subscriptions.size === 0) {\n this.disconnect();\n }\n }\n }\n };\n }\n\n /**\n * Register a convenience update callback for all subscriptions.\n * Returns an unsubscribe function.\n */\n onUpdate(callback: UpdateCallback): () => void {\n // Store as a special \"global\" callback keyed by '*'\n if (!this.subscriptions.has('*')) {\n this.subscriptions.set('*', new Set());\n }\n this.subscriptions.get('*')!.add(callback);\n\n return () => {\n const callbacks = this.subscriptions.get('*');\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n this.subscriptions.delete('*');\n }\n }\n };\n }\n\n /**\n * Set a fallback poll function to use when WebSocket is disconnected.\n * The poll function will be called at the specified interval while WS is down.\n */\n setFallbackPoll(fn: () => Promise<void>, intervalMs: number): void {\n this.fallbackPollFn = fn;\n this.fallbackPollIntervalMs = intervalMs;\n // Start polling if WS is not connected\n if (!this.isConnected()) {\n this.startFallbackPolling();\n }\n }\n\n /**\n * Connect to the WebSocket server.\n */\n connect(): void {\n if (this.destroyed) return;\n if (this.ws?.readyState === WebSocketReadyState.OPEN || this.isConnecting) {\n return;\n }\n\n this.isConnecting = true;\n\n try {\n this.log(`Connecting to ${this.wsUrl}...`);\n this.ws = this.createWebSocket(this.wsUrl);\n\n this.ws.onopen = () => {\n this.log('WebSocket connected');\n this.isConnecting = false;\n this.connectionOpenedAt = Date.now();\n\n // Resubscribe to all IPNS names (excluding '*' global key)\n const names = Array.from(this.subscriptions.keys()).filter(n => n !== '*');\n if (names.length > 0) {\n this.sendSubscribe(names);\n }\n\n // Start ping interval to keep connection alive\n this.startPingInterval();\n\n // Stop fallback polling — WS is live\n this.stopFallbackPolling();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n\n this.ws.onclose = () => {\n const connectionDuration = this.connectionOpenedAt > 0\n ? Date.now() - this.connectionOpenedAt\n : 0;\n const wasStable = connectionDuration >= this.minStableConnectionMs;\n\n this.log(`WebSocket closed (duration: ${Math.round(connectionDuration / 1000)}s)`);\n\n this.isConnecting = false;\n this.connectionOpenedAt = 0;\n this.stopPingInterval();\n\n // Only reset backoff if connection was stable\n if (wasStable) {\n this.reconnectAttempts = 0;\n }\n\n // Start fallback polling while WS is down\n this.startFallbackPolling();\n\n this.scheduleReconnect();\n };\n\n this.ws.onerror = () => {\n this.log('WebSocket error');\n this.isConnecting = false;\n };\n } catch (e) {\n this.log(`Failed to connect: ${e}`);\n this.isConnecting = false;\n this.startFallbackPolling();\n this.scheduleReconnect();\n }\n }\n\n /**\n * Disconnect from the WebSocket server and clean up.\n */\n disconnect(): void {\n this.destroyed = true;\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n this.stopPingInterval();\n this.stopFallbackPolling();\n\n if (this.ws) {\n this.ws.onopen = null;\n this.ws.onclose = null;\n this.ws.onerror = null;\n this.ws.onmessage = null;\n this.ws.close();\n this.ws = null;\n }\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n }\n\n /**\n * Check if connected to the WebSocket server.\n */\n isConnected(): boolean {\n return this.ws?.readyState === WebSocketReadyState.OPEN;\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Message Handling\n // ---------------------------------------------------------------------------\n\n private handleMessage(data: string): void {\n try {\n const message = JSON.parse(data) as WebSocketMessage;\n\n switch (message.type) {\n case 'update':\n if (message.name && message.sequence !== undefined) {\n this.notifySubscribers({\n type: 'update',\n name: message.name,\n sequence: message.sequence,\n cid: message.cid ?? '',\n timestamp: message.timestamp || new Date().toISOString(),\n });\n }\n break;\n\n case 'subscribed':\n this.log(`Subscribed to ${message.names?.length || 0} names`);\n break;\n\n case 'unsubscribed':\n this.log(`Unsubscribed from ${message.names?.length || 0} names`);\n break;\n\n case 'pong':\n // Keepalive response received\n break;\n\n case 'error':\n this.log(`Server error: ${message.message}`);\n break;\n\n default:\n // Unknown message type — ignore\n break;\n }\n } catch {\n this.log('Failed to parse message');\n }\n }\n\n private notifySubscribers(update: IpnsUpdateEvent & { type: string }): void {\n // Notify name-specific subscribers\n const callbacks = this.subscriptions.get(update.name);\n if (callbacks) {\n this.log(`Update: ${update.name.slice(0, 16)}... seq=${update.sequence}`);\n for (const callback of callbacks) {\n try {\n callback(update);\n } catch {\n // Don't let callback errors break the client\n }\n }\n }\n\n // Notify global subscribers\n const globalCallbacks = this.subscriptions.get('*');\n if (globalCallbacks) {\n for (const callback of globalCallbacks) {\n try {\n callback(update);\n } catch {\n // Don't let callback errors break the client\n }\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: WebSocket Send\n // ---------------------------------------------------------------------------\n\n private sendSubscribe(names: string[]): void {\n if (this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.ws.send(JSON.stringify({ action: 'subscribe', names }));\n }\n }\n\n private sendUnsubscribe(names: string[]): void {\n if (this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.ws.send(JSON.stringify({ action: 'unsubscribe', names }));\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Reconnection\n // ---------------------------------------------------------------------------\n\n /**\n * Schedule reconnection with exponential backoff.\n * Sequence: 5s, 10s, 20s, 40s, 60s (capped)\n */\n private scheduleReconnect(): void {\n if (this.destroyed || this.reconnectTimeout) return;\n\n // Don't reconnect if no subscriptions (excluding '*')\n const realSubscriptions = Array.from(this.subscriptions.keys()).filter(n => n !== '*');\n if (realSubscriptions.length === 0) return;\n\n this.reconnectAttempts++;\n const delay = Math.min(\n this.initialReconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1),\n this.maxReconnectDelayMs,\n );\n\n this.log(`Reconnecting in ${(delay / 1000).toFixed(1)}s (attempt ${this.reconnectAttempts})...`);\n\n this.reconnectTimeout = setTimeout(() => {\n this.reconnectTimeout = null;\n this.connect();\n }, delay);\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Keepalive\n // ---------------------------------------------------------------------------\n\n private startPingInterval(): void {\n this.stopPingInterval();\n this.pingInterval = setInterval(() => {\n if (this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.ws.send(JSON.stringify({ action: 'ping' }));\n }\n }, this.pingIntervalMs);\n }\n\n private stopPingInterval(): void {\n if (this.pingInterval) {\n clearInterval(this.pingInterval);\n this.pingInterval = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Fallback Polling\n // ---------------------------------------------------------------------------\n\n private startFallbackPolling(): void {\n if (this.fallbackPollInterval || !this.fallbackPollFn || this.destroyed) return;\n\n this.log(`Starting fallback polling (${this.fallbackPollIntervalMs / 1000}s interval)`);\n\n // Run poll immediately once\n this.fallbackPollFn().catch((err) => { logger.warn('IPNS-WS', 'Fallback poll error:', err); });\n\n this.fallbackPollInterval = setInterval(() => {\n this.fallbackPollFn?.().catch((err) => { logger.warn('IPNS-WS', 'Fallback poll error:', err); });\n }, this.fallbackPollIntervalMs);\n }\n\n private stopFallbackPolling(): void {\n if (this.fallbackPollInterval) {\n clearInterval(this.fallbackPollInterval);\n this.fallbackPollInterval = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Logging\n // ---------------------------------------------------------------------------\n\n private log(message: string): void {\n logger.debug('IPNS-WS', message);\n }\n}\n","/**\n * Write-Behind Buffer for IpfsStorageProvider\n *\n * Provides non-blocking writes via double-buffering and a promise-chain\n * serial queue. Writes are accepted immediately into a pending buffer\n * and flushed to IPFS asynchronously in the background.\n */\n\nimport type { TxfStorageDataBase } from '../../../storage';\n\n// =============================================================================\n// AsyncSerialQueue\n// =============================================================================\n\n/**\n * Promise-chain-based async mutex. Serializes async operations\n * without external dependencies. Each enqueued operation waits for\n * the previous one to complete before starting.\n */\nexport class AsyncSerialQueue {\n private tail: Promise<void> = Promise.resolve();\n\n /** Enqueue an async operation. Returns when it completes. */\n enqueue<T>(fn: () => Promise<T>): Promise<T> {\n let resolve: (value: T) => void;\n let reject: (reason: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n this.tail = this.tail.then(\n () => fn().then(resolve!, reject!),\n () => fn().then(resolve!, reject!),\n );\n\n return promise;\n }\n}\n\n// =============================================================================\n// WriteBuffer\n// =============================================================================\n\n/**\n * Collects mutations (token saves/deletes + full TXF data)\n * between flush cycles. Acts as a staging area for changes\n * that haven't been persisted to IPFS yet.\n */\nexport class WriteBuffer {\n /** Full TXF data from save() calls — latest wins */\n txfData: TxfStorageDataBase | null = null;\n\n get isEmpty(): boolean {\n return this.txfData === null;\n }\n\n clear(): void {\n this.txfData = null;\n }\n\n /**\n * Merge another buffer's contents into this one (for rollback).\n * Existing (newer) mutations in `this` take precedence over `other`.\n */\n mergeFrom(other: WriteBuffer): void {\n if (other.txfData && !this.txfData) {\n this.txfData = other.txfData;\n }\n }\n}\n","/**\n * SDK2 Constants\n * Default configuration values and storage keys\n */\n\n// =============================================================================\n// Storage Keys\n// =============================================================================\n\n/** Default prefix for all storage keys */\nexport const STORAGE_PREFIX = 'sphere_' as const;\n\n/**\n * Default encryption key for wallet data\n * WARNING: This is a placeholder. In production, use user-provided password.\n * This key is used when no password is provided to encrypt/decrypt mnemonic.\n */\nexport const DEFAULT_ENCRYPTION_KEY = 'sphere-default-key' as const;\n\n/**\n * Global storage keys (one per wallet, no address index)\n * Final key format: sphere_{key}\n */\nexport const STORAGE_KEYS_GLOBAL = {\n /** Encrypted BIP39 mnemonic */\n MNEMONIC: 'mnemonic',\n /** Encrypted master private key */\n MASTER_KEY: 'master_key',\n /** BIP32 chain code */\n CHAIN_CODE: 'chain_code',\n /** HD derivation path (full path like m/44'/0'/0'/0/0) */\n DERIVATION_PATH: 'derivation_path',\n /** Base derivation path (like m/44'/0'/0' without chain/index) */\n BASE_PATH: 'base_path',\n /** Derivation mode: bip32, wif_hmac, legacy_hmac */\n DERIVATION_MODE: 'derivation_mode',\n /** Wallet source: mnemonic, file, unknown */\n WALLET_SOURCE: 'wallet_source',\n /** Wallet existence flag */\n WALLET_EXISTS: 'wallet_exists',\n /** Current active address index */\n CURRENT_ADDRESS_INDEX: 'current_address_index',\n /** Nametag cache per address (separate from tracked addresses registry) */\n ADDRESS_NAMETAGS: 'address_nametags',\n /** Active addresses registry (JSON: TrackedAddressesStorage) */\n TRACKED_ADDRESSES: 'tracked_addresses',\n /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */\n LAST_WALLET_EVENT_TS: 'last_wallet_event_ts',\n /** Group chat: last used relay URL (stale data detection) — global, same relay for all addresses */\n GROUP_CHAT_RELAY_URL: 'group_chat_relay_url',\n /** Cached token registry JSON (fetched from remote) */\n TOKEN_REGISTRY_CACHE: 'token_registry_cache',\n /** Timestamp of last token registry cache update (ms since epoch) */\n TOKEN_REGISTRY_CACHE_TS: 'token_registry_cache_ts',\n /** Cached price data JSON (from CoinGecko or other provider) */\n PRICE_CACHE: 'price_cache',\n /** Timestamp of last price cache update (ms since epoch) */\n PRICE_CACHE_TS: 'price_cache_ts',\n} as const;\n\n/**\n * Per-address storage keys (one per derived address)\n * Final key format: sphere_{DIRECT_xxx_yyy}_{key}\n * Example: sphere_DIRECT_abc123_xyz789_pending_transfers\n *\n * Note: Token data (tokens, tombstones, archived, forked) is stored via\n * TokenStorageProvider, not here. This avoids duplication.\n */\nexport const STORAGE_KEYS_ADDRESS = {\n /** Pending transfers for this address */\n PENDING_TRANSFERS: 'pending_transfers',\n /** Transfer outbox for this address */\n OUTBOX: 'outbox',\n /** Conversations for this address */\n CONVERSATIONS: 'conversations',\n /** Messages for this address */\n MESSAGES: 'messages',\n /** Transaction history for this address */\n TRANSACTION_HISTORY: 'transaction_history',\n /** Pending V5 finalization tokens (unconfirmed instant split tokens) */\n PENDING_V5_TOKENS: 'pending_v5_tokens',\n /** Group chat: joined groups for this address */\n GROUP_CHAT_GROUPS: 'group_chat_groups',\n /** Group chat: messages for this address */\n GROUP_CHAT_MESSAGES: 'group_chat_messages',\n /** Group chat: members for this address */\n GROUP_CHAT_MEMBERS: 'group_chat_members',\n /** Group chat: processed event IDs for deduplication */\n GROUP_CHAT_PROCESSED_EVENTS: 'group_chat_processed_events',\n /** Processed V5 split group IDs for Nostr re-delivery dedup */\n PROCESSED_SPLIT_GROUP_IDS: 'processed_split_group_ids',\n /** Processed V6 combined transfer IDs for Nostr re-delivery dedup */\n PROCESSED_COMBINED_TRANSFER_IDS: 'processed_combined_transfer_ids',\n} as const;\n\n/** @deprecated Use STORAGE_KEYS_GLOBAL and STORAGE_KEYS_ADDRESS instead */\nexport const STORAGE_KEYS = {\n ...STORAGE_KEYS_GLOBAL,\n ...STORAGE_KEYS_ADDRESS,\n} as const;\n\n/**\n * Build a per-address storage key using address identifier\n * @param addressId - Short identifier for the address (e.g., first 8 chars of pubkey hash, or direct address hash)\n * @param key - The key from STORAGE_KEYS_ADDRESS\n * @returns Key in format: \"{addressId}_{key}\" e.g., \"a1b2c3d4_tokens\"\n */\nexport function getAddressStorageKey(addressId: string, key: string): string {\n return `${addressId}_${key}`;\n}\n\n/**\n * Create a readable address identifier from directAddress or chainPubkey\n * Format: DIRECT_first6_last6 (sanitized for filesystem/storage)\n * @param directAddress - The L3 direct address (DIRECT:xxx) or chainPubkey\n * @returns Sanitized identifier like \"DIRECT_abc123_xyz789\"\n */\nexport function getAddressId(directAddress: string): string {\n // Remove DIRECT:// or DIRECT: prefix if present\n let hash = directAddress;\n if (hash.startsWith('DIRECT://')) {\n hash = hash.slice(9);\n } else if (hash.startsWith('DIRECT:')) {\n hash = hash.slice(7);\n }\n // Format: DIRECT_first6_last6 (sanitized)\n const first = hash.slice(0, 6).toLowerCase();\n const last = hash.slice(-6).toLowerCase();\n return `DIRECT_${first}_${last}`;\n}\n\n// =============================================================================\n// Nostr Defaults\n// =============================================================================\n\n/** Default Nostr relays */\nexport const DEFAULT_NOSTR_RELAYS = [\n 'wss://relay.unicity.network',\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.nostr.band',\n] as const;\n\n/** Nostr event kinds used by SDK - must match @unicitylabs/nostr-js-sdk */\nexport const NOSTR_EVENT_KINDS = {\n /** NIP-04 encrypted direct message */\n DIRECT_MESSAGE: 4,\n /** Token transfer (Unicity custom - 31113) */\n TOKEN_TRANSFER: 31113,\n /** Payment request (Unicity custom - 31115) */\n PAYMENT_REQUEST: 31115,\n /** Payment request response (Unicity custom - 31116) */\n PAYMENT_REQUEST_RESPONSE: 31116,\n /** Nametag binding (NIP-78 app-specific data) */\n NAMETAG_BINDING: 30078,\n /** Public broadcast */\n BROADCAST: 1,\n} as const;\n\n/**\n * NIP-29 Event Kinds for relay-based group chat\n * https://github.com/nostr-protocol/nips/blob/master/29.md\n */\nexport const NIP29_KINDS = {\n /** Chat message sent to group */\n CHAT_MESSAGE: 9,\n /** Thread root message */\n THREAD_ROOT: 11,\n /** Thread reply message */\n THREAD_REPLY: 12,\n /** User join request */\n JOIN_REQUEST: 9021,\n /** User leave request */\n LEAVE_REQUEST: 9022,\n /** Admin: add/update user */\n PUT_USER: 9000,\n /** Admin: remove user */\n REMOVE_USER: 9001,\n /** Admin: edit group metadata */\n EDIT_METADATA: 9002,\n /** Admin: delete event */\n DELETE_EVENT: 9005,\n /** Admin: create group */\n CREATE_GROUP: 9007,\n /** Admin: delete group */\n DELETE_GROUP: 9008,\n /** Admin: create invite code */\n CREATE_INVITE: 9009,\n /** Relay-signed group metadata */\n GROUP_METADATA: 39000,\n /** Relay-signed group admins */\n GROUP_ADMINS: 39001,\n /** Relay-signed group members */\n GROUP_MEMBERS: 39002,\n /** Relay-signed group roles */\n GROUP_ROLES: 39003,\n} as const;\n\n// =============================================================================\n// Aggregator (Oracle) Defaults\n// =============================================================================\n\n/**\n * Default aggregator URL\n * Note: The aggregator is conceptually an oracle - a trusted service that provides\n * verifiable truth about token state through cryptographic inclusion proofs.\n */\nexport const DEFAULT_AGGREGATOR_URL = 'https://aggregator.unicity.network/rpc' as const;\n\n/** Dev aggregator URL */\nexport const DEV_AGGREGATOR_URL = 'https://dev-aggregator.dyndns.org/rpc' as const;\n\n/** Test aggregator URL (Goggregator) */\nexport const TEST_AGGREGATOR_URL = 'https://goggregator-test.unicity.network' as const;\n\n/** Default aggregator request timeout (ms) */\nexport const DEFAULT_AGGREGATOR_TIMEOUT = 30000;\n\n/** Default API key for aggregator authentication */\nexport const DEFAULT_AGGREGATOR_API_KEY = 'sk_06365a9c44654841a366068bcfc68986' as const;\n\n// =============================================================================\n// IPFS Defaults\n// =============================================================================\n\n/** Default IPFS gateways */\nexport const DEFAULT_IPFS_GATEWAYS = [\n 'https://unicity-ipfs1.dyndns.org',\n] as const;\n\n/** Unicity IPFS bootstrap peers */\nexport const DEFAULT_IPFS_BOOTSTRAP_PEERS = [\n '/dns4/unicity-ipfs2.dyndns.org/tcp/4001/p2p/12D3KooWLNi5NDPPHbrfJakAQqwBqymYTTwMQXQKEWuCrJNDdmfh',\n '/dns4/unicity-ipfs3.dyndns.org/tcp/4001/p2p/12D3KooWQ4aujVE4ShLjdusNZBdffq3TbzrwT2DuWZY9H1Gxhwn6',\n '/dns4/unicity-ipfs4.dyndns.org/tcp/4001/p2p/12D3KooWJ1ByPfUzUrpYvgxKU8NZrR8i6PU1tUgMEbQX9Hh2DEn1',\n '/dns4/unicity-ipfs5.dyndns.org/tcp/4001/p2p/12D3KooWB1MdZZGHN5B8TvWXntbycfe7Cjcz7n6eZ9eykZadvmDv',\n] as const;\n\n/** Unicity dedicated IPFS nodes (HTTP API access) */\nexport const UNICITY_IPFS_NODES = [\n {\n host: 'unicity-ipfs1.dyndns.org',\n peerId: '12D3KooWDKJqEMAhH4nsSSiKtK1VLcas5coUqSPZAfbWbZpxtL4u',\n httpPort: 9080,\n httpsPort: 443,\n },\n] as const;\n\n/**\n * Get IPFS gateway URLs for HTTP API access.\n * @param isSecure - Use HTTPS (default: true). Set false for development.\n */\nexport function getIpfsGatewayUrls(isSecure?: boolean): string[] {\n return UNICITY_IPFS_NODES.map((node) =>\n isSecure !== false\n ? `https://${node.host}`\n : `http://${node.host}:${node.httpPort}`,\n );\n}\n\n// =============================================================================\n// Wallet Defaults\n// =============================================================================\n\n/** Default BIP32 base path (without chain/index) */\nexport const DEFAULT_BASE_PATH = \"m/44'/0'/0'\" as const;\n\n/** Default BIP32 derivation path (full path with chain/index) */\nexport const DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0` as const;\n\n/** Coin types */\nexport const COIN_TYPES = {\n /** ALPHA token (L1 blockchain) */\n ALPHA: 'ALPHA',\n /** Test token */\n TEST: 'TEST',\n} as const;\n\n// =============================================================================\n// L1 (ALPHA Blockchain) Defaults\n// =============================================================================\n\n/** Default Fulcrum electrum server for mainnet */\nexport const DEFAULT_ELECTRUM_URL = 'wss://fulcrum.unicity.network:50004' as const;\n\n/** Testnet Fulcrum electrum server */\nexport const TEST_ELECTRUM_URL = 'wss://fulcrum.unicity.network:50004' as const;\n\n// =============================================================================\n// Token Registry Defaults\n// =============================================================================\n\n/** Remote token registry URL (GitHub raw) */\nexport const TOKEN_REGISTRY_URL =\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json' as const;\n\n/** Default token registry refresh interval (ms) — 1 hour */\nexport const TOKEN_REGISTRY_REFRESH_INTERVAL = 3_600_000;\n\n// =============================================================================\n// Network Defaults\n// =============================================================================\n\n/** Testnet Nostr relays */\nexport const TEST_NOSTR_RELAYS = [\n 'wss://nostr-relay.testnet.unicity.network',\n] as const;\n\n/** Default group chat relays (NIP-29 Zooid relay) */\nexport const DEFAULT_GROUP_RELAYS = [\n 'wss://sphere-relay.unicity.network',\n] as const;\n\n/** Network configurations */\nexport const NETWORKS = {\n mainnet: {\n name: 'Mainnet',\n aggregatorUrl: DEFAULT_AGGREGATOR_URL,\n nostrRelays: DEFAULT_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: DEFAULT_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n testnet: {\n name: 'Testnet',\n aggregatorUrl: TEST_AGGREGATOR_URL,\n nostrRelays: TEST_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: TEST_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n dev: {\n name: 'Development',\n aggregatorUrl: DEV_AGGREGATOR_URL,\n nostrRelays: TEST_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: TEST_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n} as const;\n\nexport type NetworkType = keyof typeof NETWORKS;\nexport type NetworkConfig = (typeof NETWORKS)[NetworkType];\n\n// =============================================================================\n// Timeouts & Limits\n// =============================================================================\n\n/** Default timeouts (ms) */\nexport const TIMEOUTS = {\n /** WebSocket connection timeout */\n WEBSOCKET_CONNECT: 10000,\n /** Nostr relay reconnect delay */\n NOSTR_RECONNECT_DELAY: 3000,\n /** Max reconnect attempts */\n MAX_RECONNECT_ATTEMPTS: 5,\n /** Proof polling interval */\n PROOF_POLL_INTERVAL: 1000,\n /** Sync interval */\n SYNC_INTERVAL: 60000,\n} as const;\n\n// =============================================================================\n// Sphere Connect\n// =============================================================================\n\n/** Signal sent by wallet popup to dApp when ConnectHost is ready */\nexport const HOST_READY_TYPE = 'sphere-connect:host-ready' as const;\n\n/** Default timeout (ms) for waiting for the host-ready signal */\nexport const HOST_READY_TIMEOUT = 30_000;\n\n/** Validation limits */\nexport const LIMITS = {\n /** Min nametag length */\n NAMETAG_MIN_LENGTH: 3,\n /** Max nametag length */\n NAMETAG_MAX_LENGTH: 20,\n /** Max memo length */\n MEMO_MAX_LENGTH: 500,\n /** Max message length */\n MESSAGE_MAX_LENGTH: 10000,\n} as const;\n","/**\n * IPFS Storage Provider\n * Main TokenStorageProvider implementation using IPFS/IPNS.\n * Shared cross-platform module (browser + Node.js via native fetch).\n *\n * Uses a write-behind buffer for non-blocking save() operations.\n * Writes are accepted immediately and flushed to IPFS asynchronously.\n */\n\nimport { logger } from '../../../core/logger';\nimport { SphereError } from '../../../core/errors';\nimport type { ProviderStatus, FullIdentity } from '../../../types';\nimport type {\n TokenStorageProvider,\n TxfStorageDataBase,\n SaveResult,\n LoadResult,\n SyncResult,\n StorageEventCallback,\n StorageEvent,\n} from '../../../storage';\nimport type {\n IpfsStorageConfig,\n IpfsStatePersistence,\n} from './ipfs-types';\nimport type { WebSocketFactory } from '../../../transport/websocket';\nimport { getIpfsGatewayUrls } from '../../../constants';\nimport { IpfsCache } from './ipfs-cache';\nimport { IpfsHttpClient } from './ipfs-http-client';\nimport { IpnsSubscriptionClient } from './ipns-subscription-client';\nimport { deriveIpnsIdentity } from './ipns-key-derivation';\nimport { createSignedRecord } from './ipns-record-manager';\nimport { mergeTxfData } from './txf-merge';\nimport { InMemoryIpfsStatePersistence } from './ipfs-state-persistence';\nimport { AsyncSerialQueue, WriteBuffer } from './write-behind-buffer';\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\nexport class IpfsStorageProvider<TData extends TxfStorageDataBase = TxfStorageDataBase>\n implements TokenStorageProvider<TData>\n{\n readonly id = 'ipfs';\n readonly name = 'IPFS Storage';\n readonly type = 'p2p' as const;\n\n private status: ProviderStatus = 'disconnected';\n private identity: FullIdentity | null = null;\n private ipnsKeyPair: unknown = null;\n private ipnsName: string | null = null;\n private ipnsSequenceNumber: bigint = 0n;\n private lastCid: string | null = null;\n private lastKnownRemoteSequence: bigint = 0n;\n private dataVersion = 0;\n\n /**\n * The CID currently stored on the sidecar for this IPNS name.\n * Used as `_meta.lastCid` in the next save to satisfy chain validation.\n * - null for bootstrap (first-ever save)\n * - set after every successful save() or load()\n */\n private remoteCid: string | null = null;\n\n private readonly cache: IpfsCache;\n private readonly httpClient: IpfsHttpClient;\n private readonly statePersistence: IpfsStatePersistence;\n private readonly eventCallbacks: Set<StorageEventCallback> = new Set();\n private readonly debug: boolean;\n private readonly ipnsLifetimeMs: number;\n\n /** WebSocket factory for push subscriptions */\n private readonly createWebSocket: WebSocketFactory | undefined;\n /** Override WS URL */\n private readonly wsUrl: string | undefined;\n /** Fallback poll interval (default: 90000) */\n private readonly fallbackPollIntervalMs: number;\n /** IPNS subscription client for push notifications */\n private subscriptionClient: IpnsSubscriptionClient | null = null;\n /** Unsubscribe function from subscription client */\n private subscriptionUnsubscribe: (() => void) | null = null;\n\n /** Write-behind buffer: serializes flush / sync / shutdown */\n private readonly flushQueue = new AsyncSerialQueue();\n /** Pending mutations not yet flushed to IPFS */\n private pendingBuffer = new WriteBuffer();\n /** Debounce timer for background flush */\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n /** Debounce interval in ms */\n private readonly flushDebounceMs: number;\n /** Set to true during shutdown to prevent new flushes */\n private isShuttingDown = false;\n\n constructor(\n config?: IpfsStorageConfig,\n statePersistence?: IpfsStatePersistence,\n ) {\n const gateways = config?.gateways ?? getIpfsGatewayUrls();\n this.debug = config?.debug ?? false;\n this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? (99 * 365 * 24 * 60 * 60 * 1000);\n this.flushDebounceMs = config?.flushDebounceMs ?? 2000;\n\n this.cache = new IpfsCache({\n ipnsTtlMs: config?.ipnsCacheTtlMs,\n failureCooldownMs: config?.circuitBreakerCooldownMs,\n failureThreshold: config?.circuitBreakerThreshold,\n knownFreshWindowMs: config?.knownFreshWindowMs,\n });\n\n this.httpClient = new IpfsHttpClient({\n gateways,\n fetchTimeoutMs: config?.fetchTimeoutMs,\n resolveTimeoutMs: config?.resolveTimeoutMs,\n publishTimeoutMs: config?.publishTimeoutMs,\n connectivityTimeoutMs: config?.connectivityTimeoutMs,\n debug: this.debug,\n }, this.cache);\n\n this.statePersistence = statePersistence ?? new InMemoryIpfsStatePersistence();\n this.createWebSocket = config?.createWebSocket;\n this.wsUrl = config?.wsUrl;\n this.fallbackPollIntervalMs = config?.fallbackPollIntervalMs ?? 90000;\n }\n\n // ---------------------------------------------------------------------------\n // BaseProvider interface\n // ---------------------------------------------------------------------------\n\n async connect(): Promise<void> {\n await this.initialize();\n }\n\n async disconnect(): Promise<void> {\n await this.shutdown();\n }\n\n isConnected(): boolean {\n return this.status === 'connected';\n }\n\n getStatus(): ProviderStatus {\n return this.status;\n }\n\n // ---------------------------------------------------------------------------\n // Identity & Initialization\n // ---------------------------------------------------------------------------\n\n setIdentity(identity: FullIdentity): void {\n this.identity = identity;\n }\n\n async initialize(): Promise<boolean> {\n if (!this.identity) {\n this.log('Cannot initialize: no identity set');\n return false;\n }\n\n this.status = 'connecting';\n this.emitEvent({ type: 'storage:loading', timestamp: Date.now() });\n\n try {\n // Derive IPNS key pair and name from wallet private key\n const { keyPair, ipnsName } = await deriveIpnsIdentity(this.identity.privateKey);\n this.ipnsKeyPair = keyPair;\n this.ipnsName = ipnsName;\n this.log(`IPNS name derived: ${ipnsName}`);\n\n // Load persisted state\n const persisted = await this.statePersistence.load(ipnsName);\n if (persisted) {\n this.ipnsSequenceNumber = BigInt(persisted.sequenceNumber);\n this.lastCid = persisted.lastCid;\n this.remoteCid = persisted.lastCid; // chain link for next save\n this.dataVersion = persisted.version;\n this.log(`Loaded persisted state: seq=${this.ipnsSequenceNumber}, cid=${this.lastCid}`);\n }\n\n // Set up IPNS push subscription if WebSocket factory is available\n if (this.createWebSocket) {\n try {\n const wsUrlFinal = this.wsUrl ?? this.deriveWsUrl();\n if (wsUrlFinal) {\n this.subscriptionClient = new IpnsSubscriptionClient({\n wsUrl: wsUrlFinal,\n createWebSocket: this.createWebSocket,\n debug: this.debug,\n });\n\n // Subscribe to own IPNS name\n this.subscriptionUnsubscribe = this.subscriptionClient.subscribe(\n ipnsName,\n (update) => {\n this.log(`Push update: seq=${update.sequence}, cid=${update.cid}`);\n this.emitEvent({\n type: 'storage:remote-updated',\n timestamp: Date.now(),\n data: { name: update.name, sequence: update.sequence, cid: update.cid },\n });\n },\n );\n\n // Set fallback poll for when WS is disconnected\n this.subscriptionClient.setFallbackPoll(\n () => this.pollForRemoteChanges(),\n this.fallbackPollIntervalMs,\n );\n\n // Connect (non-blocking)\n this.subscriptionClient.connect();\n }\n } catch (wsError) {\n this.log(`Failed to set up IPNS subscription: ${wsError}`);\n // Non-fatal — provider works without push notifications\n }\n }\n\n // Test gateway connectivity (non-blocking, don't fail on it)\n this.httpClient.findHealthyGateways().then((healthy) => {\n if (healthy.length > 0) {\n this.log(`${healthy.length} healthy gateway(s) found`);\n } else {\n this.log('Warning: no healthy gateways found');\n }\n }).catch((err) => {\n logger.warn('IPFS-Storage', 'Gateway health check failed (non-fatal):', err);\n });\n\n this.isShuttingDown = false;\n this.status = 'connected';\n this.emitEvent({ type: 'storage:loaded', timestamp: Date.now() });\n return true;\n } catch (error) {\n this.status = 'error';\n this.emitEvent({\n type: 'storage:error',\n timestamp: Date.now(),\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n }\n\n async shutdown(): Promise<void> {\n this.isShuttingDown = true;\n\n // Cancel any pending debounced flush\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n\n // Final flush — drain any pending writes\n await this.flushQueue.enqueue(async () => {\n if (!this.pendingBuffer.isEmpty) {\n try {\n await this.executeFlush();\n } catch {\n this.log('Final flush on shutdown failed (data may be lost)');\n }\n }\n });\n\n // Disconnect subscription client\n if (this.subscriptionUnsubscribe) {\n this.subscriptionUnsubscribe();\n this.subscriptionUnsubscribe = null;\n }\n if (this.subscriptionClient) {\n this.subscriptionClient.disconnect();\n this.subscriptionClient = null;\n }\n\n this.cache.clear();\n this.status = 'disconnected';\n }\n\n // ---------------------------------------------------------------------------\n // Save (non-blocking — buffers data for async flush)\n // ---------------------------------------------------------------------------\n\n async save(data: TData): Promise<SaveResult> {\n if (!this.ipnsKeyPair || !this.ipnsName) {\n return { success: false, error: 'Not initialized', timestamp: Date.now() };\n }\n\n // Buffer the data for async flush\n this.pendingBuffer.txfData = data;\n this.scheduleFlush();\n\n // Return immediately — flush happens in background\n return { success: true, timestamp: Date.now() };\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Blocking save (used by sync and executeFlush)\n // ---------------------------------------------------------------------------\n\n /**\n * Perform the actual upload + IPNS publish synchronously.\n * Called by executeFlush() and sync() — never by public save().\n */\n private async _doSave(data: TData): Promise<SaveResult> {\n if (!this.ipnsKeyPair || !this.ipnsName) {\n return { success: false, error: 'Not initialized', timestamp: Date.now() };\n }\n\n this.emitEvent({ type: 'storage:saving', timestamp: Date.now() });\n\n try {\n // Update meta with chain-validation fields required by sidecar:\n // - lastCid: must equal the CID currently stored on sidecar (null for bootstrap)\n // - version: must be exactly current_version + 1 (for normal updates)\n this.dataVersion++;\n const metaUpdate: Record<string, unknown> = {\n ...data._meta,\n version: this.dataVersion,\n ipnsName: this.ipnsName,\n updatedAt: Date.now(),\n };\n if (this.remoteCid) {\n // Normal update: chain to previous CID\n metaUpdate.lastCid = this.remoteCid;\n }\n // Bootstrap (remoteCid is null): do NOT include lastCid field at all\n const updatedData = { ...data, _meta: metaUpdate } as unknown as Record<string, unknown>;\n\n // Upload to IPFS\n const { cid } = await this.httpClient.upload(updatedData);\n this.log(`Content uploaded: CID=${cid}`);\n\n // Compute new sequence: max(local, remote) + 1\n const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence\n ? this.ipnsSequenceNumber\n : this.lastKnownRemoteSequence;\n const newSeq = baseSeq + 1n;\n\n // Create signed IPNS record\n const marshalledRecord = await createSignedRecord(\n this.ipnsKeyPair,\n cid,\n newSeq,\n this.ipnsLifetimeMs,\n );\n\n // Publish to all gateways\n const publishResult = await this.httpClient.publishIpns(\n this.ipnsName,\n marshalledRecord,\n );\n\n if (!publishResult.success) {\n // Rollback version (sequence was not yet updated)\n this.dataVersion--;\n this.log(`IPNS publish failed: ${publishResult.error}`);\n return {\n success: false,\n error: publishResult.error ?? 'IPNS publish failed',\n timestamp: Date.now(),\n };\n }\n\n // Update local state\n this.ipnsSequenceNumber = newSeq;\n this.lastCid = cid;\n this.remoteCid = cid; // next save chains to this CID\n\n // Update cache\n this.cache.setIpnsRecord(this.ipnsName, {\n cid,\n sequence: newSeq,\n gateway: 'local',\n });\n this.cache.setContent(cid, updatedData as unknown as TxfStorageDataBase);\n this.cache.markIpnsFresh(this.ipnsName);\n\n // Persist state\n await this.statePersistence.save(this.ipnsName, {\n sequenceNumber: newSeq.toString(),\n lastCid: cid,\n version: this.dataVersion,\n });\n\n this.emitEvent({\n type: 'storage:saved',\n timestamp: Date.now(),\n data: { cid, sequence: newSeq.toString() },\n });\n\n this.log(`Saved: CID=${cid}, seq=${newSeq}`);\n return { success: true, cid, timestamp: Date.now() };\n } catch (error) {\n // Rollback version on any error\n this.dataVersion--;\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emitEvent({\n type: 'storage:error',\n timestamp: Date.now(),\n error: errorMessage,\n });\n return { success: false, error: errorMessage, timestamp: Date.now() };\n }\n }\n\n // ---------------------------------------------------------------------------\n // Write-behind buffer: scheduling and flushing\n // ---------------------------------------------------------------------------\n\n /**\n * Schedule a debounced background flush.\n * Resets the timer on each call so rapid mutations coalesce.\n */\n private scheduleFlush(): void {\n if (this.isShuttingDown) return;\n if (this.flushTimer) clearTimeout(this.flushTimer);\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n this.flushQueue.enqueue(() => this.executeFlush()).catch((err) => {\n this.log(`Background flush failed: ${err}`);\n });\n }, this.flushDebounceMs);\n }\n\n /**\n * Execute a flush of the pending buffer to IPFS.\n * Runs inside AsyncSerialQueue for concurrency safety.\n */\n private async executeFlush(): Promise<void> {\n if (this.pendingBuffer.isEmpty) return;\n\n // 1. Swap: take pending → active, create new empty pending\n const active = this.pendingBuffer;\n this.pendingBuffer = new WriteBuffer();\n\n try {\n // 2. Build the data to save\n // Use buffered TXF data if available, otherwise build minimal payload\n const baseData = (active.txfData ?? {\n _meta: { version: 0, address: this.identity?.directAddress ?? '', formatVersion: '2.0', updatedAt: 0 },\n }) as TData;\n\n // 3. Perform the actual blocking save\n const result = await this._doSave(baseData);\n\n if (!result.success) {\n throw new SphereError(result.error ?? 'Save failed', 'STORAGE_ERROR');\n }\n\n this.log(`Flushed successfully: CID=${result.cid}`);\n } catch (error) {\n // 4. Rollback: merge active back into pending\n this.pendingBuffer.mergeFrom(active);\n\n const msg = error instanceof Error ? error.message : String(error);\n this.log(`Flush failed (will retry): ${msg}`);\n\n // Schedule retry\n this.scheduleFlush();\n\n throw error; // re-throw so callers (e.g. shutdown) know it failed\n }\n }\n\n // ---------------------------------------------------------------------------\n // Load\n // ---------------------------------------------------------------------------\n\n async load(identifier?: string): Promise<LoadResult<TData>> {\n if (!this.ipnsName && !identifier) {\n return { success: false, error: 'Not initialized', source: 'local', timestamp: Date.now() };\n }\n\n this.emitEvent({ type: 'storage:loading', timestamp: Date.now() });\n\n try {\n // If a specific CID is given, fetch directly\n if (identifier) {\n const data = await this.httpClient.fetchContent<TData>(identifier);\n return { success: true, data, source: 'remote', timestamp: Date.now() };\n }\n\n const ipnsName = this.ipnsName!;\n\n // Check known-fresh cache\n if (this.cache.isIpnsKnownFresh(ipnsName)) {\n const cached = this.cache.getIpnsRecordIgnoreTtl(ipnsName);\n if (cached) {\n const content = this.cache.getContent(cached.cid);\n if (content) {\n this.log('Using known-fresh cached data');\n return { success: true, data: content as TData, source: 'cache', timestamp: Date.now() };\n }\n }\n }\n\n // Check IPNS cache (60s TTL)\n const cachedRecord = this.cache.getIpnsRecord(ipnsName);\n if (cachedRecord) {\n const content = this.cache.getContent(cachedRecord.cid);\n if (content) {\n this.log('IPNS cache hit');\n return { success: true, data: content as TData, source: 'cache', timestamp: Date.now() };\n }\n // Have CID but not content — fetch content\n try {\n const data = await this.httpClient.fetchContent<TData>(cachedRecord.cid);\n return { success: true, data, source: 'remote', timestamp: Date.now() };\n } catch {\n // Fall through to full resolution\n }\n }\n\n // Resolve IPNS from network\n const { best } = await this.httpClient.resolveIpns(ipnsName);\n\n if (!best) {\n // Not found — could be a new wallet\n this.log('IPNS record not found (new wallet?)');\n return { success: false, error: 'IPNS record not found', source: 'remote', timestamp: Date.now() };\n }\n\n // Track remote sequence and CID for chain validation\n if (best.sequence > this.lastKnownRemoteSequence) {\n this.lastKnownRemoteSequence = best.sequence;\n }\n this.remoteCid = best.cid;\n\n // Fetch content\n const data = await this.httpClient.fetchContent<TData>(best.cid);\n\n // Track remote version for correct version chaining\n const remoteVersion = (data as TxfStorageDataBase)?._meta?.version;\n if (typeof remoteVersion === 'number' && remoteVersion > this.dataVersion) {\n this.dataVersion = remoteVersion;\n }\n\n this.emitEvent({\n type: 'storage:loaded',\n timestamp: Date.now(),\n data: { cid: best.cid, sequence: best.sequence.toString() },\n });\n\n return { success: true, data, source: 'remote', timestamp: Date.now() };\n } catch (error) {\n // On network error, try to return cached data\n if (this.ipnsName) {\n const cached = this.cache.getIpnsRecordIgnoreTtl(this.ipnsName);\n if (cached) {\n const content = this.cache.getContent(cached.cid);\n if (content) {\n this.log('Network error, returning stale cache');\n return { success: true, data: content as TData, source: 'cache', timestamp: Date.now() };\n }\n }\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emitEvent({\n type: 'storage:error',\n timestamp: Date.now(),\n error: errorMessage,\n });\n return { success: false, error: errorMessage, source: 'remote', timestamp: Date.now() };\n }\n }\n\n // ---------------------------------------------------------------------------\n // Sync (enters serial queue to avoid concurrent IPNS conflicts)\n // ---------------------------------------------------------------------------\n\n async sync(localData: TData): Promise<SyncResult<TData>> {\n return this.flushQueue.enqueue(async () => {\n // Cancel any pending debounced flush (we'll save as part of sync)\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n\n this.emitEvent({ type: 'sync:started', timestamp: Date.now() });\n\n try {\n // Drain pending buffer — its data will be included via the sync save\n this.pendingBuffer.clear();\n\n // Load remote data\n const remoteResult = await this.load();\n\n if (!remoteResult.success || !remoteResult.data) {\n // No remote data — save local as initial\n this.log('No remote data found, uploading local data');\n const saveResult = await this._doSave(localData);\n this.emitEvent({ type: 'sync:completed', timestamp: Date.now() });\n return {\n success: saveResult.success,\n merged: localData,\n added: 0,\n removed: 0,\n conflicts: 0,\n error: saveResult.error,\n };\n }\n\n const remoteData = remoteResult.data;\n\n // Check if merge is needed\n const localVersion = localData._meta?.version ?? 0;\n const remoteVersion = remoteData._meta?.version ?? 0;\n\n if (localVersion === remoteVersion && this.lastCid) {\n // Same version — no merge needed\n this.log('Data is in sync (same version)');\n this.emitEvent({ type: 'sync:completed', timestamp: Date.now() });\n return {\n success: true,\n merged: localData,\n added: 0,\n removed: 0,\n conflicts: 0,\n };\n }\n\n // Merge\n this.log(`Merging: local v${localVersion} <-> remote v${remoteVersion}`);\n const { merged, added, removed, conflicts } = mergeTxfData(localData, remoteData);\n\n if (conflicts > 0) {\n this.emitEvent({\n type: 'sync:conflict',\n timestamp: Date.now(),\n data: { conflicts },\n });\n }\n\n // Save merged result\n const saveResult = await this._doSave(merged);\n\n this.emitEvent({\n type: 'sync:completed',\n timestamp: Date.now(),\n data: { added, removed, conflicts },\n });\n\n return {\n success: saveResult.success,\n merged: merged,\n added,\n removed,\n conflicts,\n error: saveResult.error,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emitEvent({\n type: 'sync:error',\n timestamp: Date.now(),\n error: errorMessage,\n });\n return {\n success: false,\n added: 0,\n removed: 0,\n conflicts: 0,\n error: errorMessage,\n };\n }\n });\n }\n\n // ---------------------------------------------------------------------------\n // Private Helpers\n // ---------------------------------------------------------------------------\n\n // ---------------------------------------------------------------------------\n // Optional Methods\n // ---------------------------------------------------------------------------\n\n async exists(): Promise<boolean> {\n if (!this.ipnsName) return false;\n\n // Check cache first\n const cached = this.cache.getIpnsRecord(this.ipnsName);\n if (cached) return true;\n\n // Resolve from network\n const { best } = await this.httpClient.resolveIpns(this.ipnsName);\n return best !== null;\n }\n\n async clear(): Promise<boolean> {\n if (!this.ipnsKeyPair || !this.ipnsName) return false;\n\n // Clear pending buffer\n this.pendingBuffer.clear();\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n\n const emptyData = {\n _meta: {\n version: 0,\n address: this.identity?.directAddress ?? '',\n ipnsName: this.ipnsName,\n formatVersion: '2.0',\n updatedAt: Date.now(),\n },\n } as TData;\n\n const result = await this._doSave(emptyData);\n if (result.success) {\n this.cache.clear();\n await this.statePersistence.clear(this.ipnsName);\n }\n return result.success;\n }\n\n onEvent(callback: StorageEventCallback): () => void {\n this.eventCallbacks.add(callback);\n return () => {\n this.eventCallbacks.delete(callback);\n };\n }\n\n // ---------------------------------------------------------------------------\n // Public Accessors\n // ---------------------------------------------------------------------------\n\n getIpnsName(): string | null {\n return this.ipnsName;\n }\n\n getLastCid(): string | null {\n return this.lastCid;\n }\n\n getSequenceNumber(): bigint {\n return this.ipnsSequenceNumber;\n }\n\n getDataVersion(): number {\n return this.dataVersion;\n }\n\n getRemoteCid(): string | null {\n return this.remoteCid;\n }\n\n // ---------------------------------------------------------------------------\n // Testing helper: wait for pending flush to complete\n // ---------------------------------------------------------------------------\n\n /**\n * Wait for the pending flush timer to fire and the flush operation to\n * complete. Useful in tests to await background writes.\n * Returns immediately if no flush is pending.\n */\n async waitForFlush(): Promise<void> {\n if (this.flushTimer) {\n // Force the timer to fire now\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n await this.flushQueue.enqueue(() => this.executeFlush()).catch((err) => {\n logger.warn('IPFS-Storage', 'Flush on shutdown failed:', err);\n });\n } else if (!this.pendingBuffer.isEmpty) {\n // No timer but pending data — flush now\n await this.flushQueue.enqueue(() => this.executeFlush()).catch((err) => {\n logger.warn('IPFS-Storage', 'Flush on shutdown failed:', err);\n });\n } else {\n // Ensure any in-flight flush completes\n await this.flushQueue.enqueue(async () => {});\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Push Subscription Helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Derive WebSocket URL from the first configured gateway.\n * Converts https://host → wss://host/ws/ipns\n */\n private deriveWsUrl(): string | null {\n const gateways = this.httpClient.getGateways();\n if (gateways.length === 0) return null;\n\n const gateway = gateways[0];\n const wsProtocol = gateway.startsWith('https://') ? 'wss://' : 'ws://';\n const host = gateway.replace(/^https?:\\/\\//, '');\n return `${wsProtocol}${host}/ws/ipns`;\n }\n\n /**\n * Poll for remote IPNS changes (fallback when WS is unavailable).\n * Compares remote sequence number with last known and emits event if changed.\n */\n private async pollForRemoteChanges(): Promise<void> {\n if (!this.ipnsName) return;\n\n try {\n const { best } = await this.httpClient.resolveIpns(this.ipnsName);\n if (best && best.sequence > this.lastKnownRemoteSequence) {\n this.log(`Poll detected remote change: seq=${best.sequence} (was ${this.lastKnownRemoteSequence})`);\n this.lastKnownRemoteSequence = best.sequence;\n this.emitEvent({\n type: 'storage:remote-updated',\n timestamp: Date.now(),\n data: { name: this.ipnsName, sequence: Number(best.sequence), cid: best.cid },\n });\n }\n } catch {\n // Non-fatal — poll will retry on next interval\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private emitEvent(event: StorageEvent): void {\n for (const callback of this.eventCallbacks) {\n try {\n callback(event);\n } catch {\n // Don't let event handler errors break the provider\n }\n }\n }\n\n private log(message: string): void {\n logger.debug('IPFS-Storage', message);\n }\n\n}\n","/**\n * Browser IPFS State Persistence\n * Uses localStorage for persisting IPFS/IPNS state between sessions\n */\n\nimport type { IpfsStatePersistence, IpfsPersistedState } from '../../shared/ipfs';\n\nconst KEY_PREFIX = 'sphere_ipfs_';\n\nfunction seqKey(ipnsName: string): string {\n return `${KEY_PREFIX}seq_${ipnsName}`;\n}\n\nfunction cidKey(ipnsName: string): string {\n return `${KEY_PREFIX}cid_${ipnsName}`;\n}\n\nfunction verKey(ipnsName: string): string {\n return `${KEY_PREFIX}ver_${ipnsName}`;\n}\n\nexport class BrowserIpfsStatePersistence implements IpfsStatePersistence {\n async load(ipnsName: string): Promise<IpfsPersistedState | null> {\n try {\n const seq = localStorage.getItem(seqKey(ipnsName));\n if (!seq) return null;\n\n return {\n sequenceNumber: seq,\n lastCid: localStorage.getItem(cidKey(ipnsName)),\n version: parseInt(localStorage.getItem(verKey(ipnsName)) ?? '0', 10),\n };\n } catch {\n return null;\n }\n }\n\n async save(ipnsName: string, state: IpfsPersistedState): Promise<void> {\n try {\n localStorage.setItem(seqKey(ipnsName), state.sequenceNumber);\n if (state.lastCid) {\n localStorage.setItem(cidKey(ipnsName), state.lastCid);\n } else {\n localStorage.removeItem(cidKey(ipnsName));\n }\n localStorage.setItem(verKey(ipnsName), String(state.version));\n } catch {\n // localStorage might be full or unavailable\n }\n }\n\n async clear(ipnsName: string): Promise<void> {\n try {\n localStorage.removeItem(seqKey(ipnsName));\n localStorage.removeItem(cidKey(ipnsName));\n localStorage.removeItem(verKey(ipnsName));\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n","/**\n * Browser IPFS Storage Module\n * Factory function for browser-specific IPFS storage provider\n */\n\nimport { IpfsStorageProvider, type IpfsStorageConfig } from '../../shared/ipfs';\nimport { BrowserIpfsStatePersistence } from './browser-ipfs-state-persistence';\nimport type { IWebSocket } from '../../../transport/websocket';\n\n// Re-export for convenience\nexport { IpfsStorageProvider } from '../../shared/ipfs';\nexport { BrowserIpfsStatePersistence } from './browser-ipfs-state-persistence';\nexport type { IpfsStorageConfig as IpfsStorageProviderConfig } from '../../shared/ipfs';\n\n/**\n * Create a browser WebSocket that conforms to the IWebSocket interface.\n */\nfunction createBrowserWebSocket(url: string): IWebSocket {\n return new WebSocket(url) as unknown as IWebSocket;\n}\n\n/**\n * Create a browser IPFS storage provider with localStorage-based state persistence.\n * Automatically injects the browser WebSocket factory for IPNS push subscriptions.\n */\nexport function createBrowserIpfsStorageProvider(config?: IpfsStorageConfig): IpfsStorageProvider {\n return new IpfsStorageProvider(\n { ...config, createWebSocket: config?.createWebSocket ?? createBrowserWebSocket },\n new BrowserIpfsStatePersistence(),\n );\n}\n\n/** @deprecated Use createBrowserIpfsStorageProvider instead */\nexport const createIpfsStorageProvider = createBrowserIpfsStorageProvider;\n"],"mappings":"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","names":["elliptic","data","saveResult"]}
1
+ {"version":3,"sources":["../../../impl/browser/ipfs.ts","../../../impl/shared/ipfs/ipfs-error-types.ts","../../../impl/shared/ipfs/ipfs-state-persistence.ts","../../../node_modules/@noble/hashes/src/utils.ts","../../../node_modules/@noble/hashes/src/hmac.ts","../../../node_modules/@noble/hashes/src/hkdf.ts","../../../node_modules/@noble/hashes/src/_md.ts","../../../node_modules/@noble/hashes/src/sha2.ts","../../../core/crypto.ts","../../../core/errors.ts","../../../impl/shared/ipfs/ipns-key-derivation.ts","../../../impl/shared/ipfs/ipns-record-manager.ts","../../../impl/shared/ipfs/ipfs-cache.ts","../../../core/logger.ts","../../../impl/shared/ipfs/ipfs-http-client.ts","../../../impl/shared/ipfs/txf-merge.ts","../../../transport/websocket.ts","../../../impl/shared/ipfs/ipns-subscription-client.ts","../../../impl/shared/ipfs/write-behind-buffer.ts","../../../constants.ts","../../../impl/shared/ipfs/ipfs-storage-provider.ts","../../../impl/browser/ipfs/browser-ipfs-state-persistence.ts","../../../impl/browser/ipfs/index.ts"],"sourcesContent":["/**\n * Browser IPFS Storage Provider\n *\n * Separate entry point for IPFS functionality.\n *\n * @example\n * ```ts\n * import { IpfsStorageProvider, createIpfsStorageProvider } from '@unicitylabs/sphere-sdk/impl/browser/ipfs';\n * ```\n */\nexport {\n IpfsStorageProvider,\n createBrowserIpfsStorageProvider,\n createIpfsStorageProvider,\n BrowserIpfsStatePersistence,\n type IpfsStorageProviderConfig,\n} from './ipfs/index';\n","/**\n * IPFS Error Classification\n * Categorizes errors for proper handling (e.g., NOT_FOUND should not trigger circuit breaker)\n */\n\n// =============================================================================\n// Error Categories\n// =============================================================================\n\nexport type IpfsErrorCategory =\n | 'NOT_FOUND' // IPNS record never published (expected for new wallets)\n | 'NETWORK_ERROR' // Connectivity / server issues\n | 'TIMEOUT' // Request timed out\n | 'GATEWAY_ERROR' // Gateway returned error (5xx, etc.)\n | 'INVALID_RESPONSE' // Response parsing failed\n | 'CID_MISMATCH' // Content hash doesn't match CID\n | 'SEQUENCE_DOWNGRADE'; // Remote sequence < local (stale data)\n\n// =============================================================================\n// Error Class\n// =============================================================================\n\nexport class IpfsError extends Error {\n readonly category: IpfsErrorCategory;\n readonly gateway?: string;\n readonly cause?: Error;\n\n constructor(\n message: string,\n category: IpfsErrorCategory,\n gateway?: string,\n cause?: Error,\n ) {\n super(message);\n this.name = 'IpfsError';\n this.category = category;\n this.gateway = gateway;\n this.cause = cause;\n }\n\n /** Whether this error should trigger the circuit breaker */\n get shouldTriggerCircuitBreaker(): boolean {\n // NOT_FOUND is expected for new wallets, don't penalize the gateway\n return this.category !== 'NOT_FOUND' && this.category !== 'SEQUENCE_DOWNGRADE';\n }\n}\n\n// =============================================================================\n// Error Classification Helpers\n// =============================================================================\n\n/**\n * Classify a fetch exception into an IpfsErrorCategory\n */\nexport function classifyFetchError(error: unknown): IpfsErrorCategory {\n if (error instanceof DOMException && error.name === 'AbortError') {\n return 'TIMEOUT';\n }\n if (error instanceof TypeError) {\n // TypeError typically means network failure (DNS, connection refused, etc.)\n return 'NETWORK_ERROR';\n }\n if (error instanceof Error && error.name === 'TimeoutError') {\n return 'TIMEOUT';\n }\n return 'NETWORK_ERROR';\n}\n\n/**\n * Classify an HTTP status code into an IpfsErrorCategory\n * @param status - HTTP status code\n * @param responseBody - Optional response body for additional context\n */\nexport function classifyHttpStatus(\n status: number,\n responseBody?: string,\n): IpfsErrorCategory {\n if (status === 404) {\n return 'NOT_FOUND';\n }\n\n if (status === 500 && responseBody) {\n // Kubo returns 500 with \"routing: not found\" for IPNS records that don't exist\n if (/routing:\\s*not\\s*found/i.test(responseBody)) {\n return 'NOT_FOUND';\n }\n }\n\n if (status >= 500) {\n return 'GATEWAY_ERROR';\n }\n\n if (status >= 400) {\n return 'GATEWAY_ERROR';\n }\n\n return 'GATEWAY_ERROR';\n}\n","/**\n * IPFS State Persistence\n * Interface and in-memory implementation for persisting IPFS/IPNS state\n */\n\nimport type { IpfsStatePersistence, IpfsPersistedState } from './ipfs-types';\n\n// Re-export for convenience\nexport type { IpfsStatePersistence, IpfsPersistedState };\n\n// =============================================================================\n// In-Memory Implementation (for testing)\n// =============================================================================\n\nexport class InMemoryIpfsStatePersistence implements IpfsStatePersistence {\n private readonly states = new Map<string, IpfsPersistedState>();\n\n async load(ipnsName: string): Promise<IpfsPersistedState | null> {\n return this.states.get(ipnsName) ?? null;\n }\n\n async save(ipnsName: string, state: IpfsPersistedState): Promise<void> {\n this.states.set(ipnsName, { ...state });\n }\n\n async clear(ipnsName: string): Promise<void> {\n this.states.delete(ipnsName);\n }\n}\n","/**\n * Utilities for hex, bytes, CSPRNG.\n * @module\n */\n/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */\n/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */\nexport function isBytes(a: unknown): a is Uint8Array {\n return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');\n}\n\n/** Asserts something is positive integer. */\nexport function anumber(n: number, title: string = ''): void {\n if (!Number.isSafeInteger(n) || n < 0) {\n const prefix = title && `\"${title}\" `;\n throw new Error(`${prefix}expected integer >= 0, got ${n}`);\n }\n}\n\n/** Asserts something is Uint8Array. */\nexport function abytes(value: Uint8Array, length?: number, title: string = ''): Uint8Array {\n const bytes = isBytes(value);\n const len = value?.length;\n const needsLen = length !== undefined;\n if (!bytes || (needsLen && len !== length)) {\n const prefix = title && `\"${title}\" `;\n const ofLen = needsLen ? ` of length ${length}` : '';\n const got = bytes ? `length=${len}` : `type=${typeof value}`;\n throw new Error(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);\n }\n return value;\n}\n\n/** Asserts something is hash */\nexport function ahash(h: CHash): void {\n if (typeof h !== 'function' || typeof h.create !== 'function')\n throw new Error('Hash must wrapped by utils.createHasher');\n anumber(h.outputLen);\n anumber(h.blockLen);\n}\n\n/** Asserts a hash instance has not been destroyed / finished */\nexport function aexists(instance: any, checkFinished = true): void {\n if (instance.destroyed) throw new Error('Hash instance has been destroyed');\n if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called');\n}\n\n/** Asserts output is properly-sized byte array */\nexport function aoutput(out: any, instance: any): void {\n abytes(out, undefined, 'digestInto() output');\n const min = instance.outputLen;\n if (out.length < min) {\n throw new Error('\"digestInto() output\" expected to be of length >=' + min);\n }\n}\n\n/** Generic type encompassing 8/16/32-byte arrays - but not 64-byte. */\n// prettier-ignore\nexport type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array |\n Uint16Array | Int16Array | Uint32Array | Int32Array;\n\n/** Cast u8 / u16 / u32 to u8. */\nexport function u8(arr: TypedArray): Uint8Array {\n return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/** Cast u8 / u16 / u32 to u32. */\nexport function u32(arr: TypedArray): Uint32Array {\n return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));\n}\n\n/** Zeroize a byte array. Warning: JS provides no guarantees. */\nexport function clean(...arrays: TypedArray[]): void {\n for (let i = 0; i < arrays.length; i++) {\n arrays[i].fill(0);\n }\n}\n\n/** Create DataView of an array for easy byte-level manipulation. */\nexport function createView(arr: TypedArray): DataView {\n return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/** The rotate right (circular right shift) operation for uint32 */\nexport function rotr(word: number, shift: number): number {\n return (word << (32 - shift)) | (word >>> shift);\n}\n\n/** The rotate left (circular left shift) operation for uint32 */\nexport function rotl(word: number, shift: number): number {\n return (word << shift) | ((word >>> (32 - shift)) >>> 0);\n}\n\n/** Is current platform little-endian? Most are. Big-Endian platform: IBM */\nexport const isLE: boolean = /* @__PURE__ */ (() =>\n new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();\n\n/** The byte swap operation for uint32 */\nexport function byteSwap(word: number): number {\n return (\n ((word << 24) & 0xff000000) |\n ((word << 8) & 0xff0000) |\n ((word >>> 8) & 0xff00) |\n ((word >>> 24) & 0xff)\n );\n}\n/** Conditionally byte swap if on a big-endian platform */\nexport const swap8IfBE: (n: number) => number = isLE\n ? (n: number) => n\n : (n: number) => byteSwap(n);\n\n/** In place byte swap for Uint32Array */\nexport function byteSwap32(arr: Uint32Array): Uint32Array {\n for (let i = 0; i < arr.length; i++) {\n arr[i] = byteSwap(arr[i]);\n }\n return arr;\n}\n\nexport const swap32IfBE: (u: Uint32Array) => Uint32Array = isLE\n ? (u: Uint32Array) => u\n : byteSwap32;\n\n// Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex\nconst hasHexBuiltin: boolean = /* @__PURE__ */ (() =>\n // @ts-ignore\n typeof Uint8Array.from([]).toHex === 'function' && typeof Uint8Array.fromHex === 'function')();\n\n// Array where index 0xf0 (240) is mapped to string 'f0'\nconst hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>\n i.toString(16).padStart(2, '0')\n);\n\n/**\n * Convert byte array to hex string. Uses built-in function, when available.\n * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'\n */\nexport function bytesToHex(bytes: Uint8Array): string {\n abytes(bytes);\n // @ts-ignore\n if (hasHexBuiltin) return bytes.toHex();\n // pre-caching improves the speed 6x\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += hexes[bytes[i]];\n }\n return hex;\n}\n\n// We use optimized technique to convert hex string to byte array\nconst asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const;\nfunction asciiToBase16(ch: number): number | undefined {\n if (ch >= asciis._0 && ch <= asciis._9) return ch - asciis._0; // '2' => 50-48\n if (ch >= asciis.A && ch <= asciis.F) return ch - (asciis.A - 10); // 'B' => 66-(65-10)\n if (ch >= asciis.a && ch <= asciis.f) return ch - (asciis.a - 10); // 'b' => 98-(97-10)\n return;\n}\n\n/**\n * Convert hex string to byte array. Uses built-in function, when available.\n * @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])\n */\nexport function hexToBytes(hex: string): Uint8Array {\n if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);\n // @ts-ignore\n if (hasHexBuiltin) return Uint8Array.fromHex(hex);\n const hl = hex.length;\n const al = hl / 2;\n if (hl % 2) throw new Error('hex string expected, got unpadded hex of length ' + hl);\n const array = new Uint8Array(al);\n for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {\n const n1 = asciiToBase16(hex.charCodeAt(hi));\n const n2 = asciiToBase16(hex.charCodeAt(hi + 1));\n if (n1 === undefined || n2 === undefined) {\n const char = hex[hi] + hex[hi + 1];\n throw new Error('hex string expected, got non-hex character \"' + char + '\" at index ' + hi);\n }\n array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163\n }\n return array;\n}\n\n/**\n * There is no setImmediate in browser and setTimeout is slow.\n * Call of async fn will return Promise, which will be fullfiled only on\n * next scheduler queue processing step and this is exactly what we need.\n */\nexport const nextTick = async (): Promise<void> => {};\n\n/** Returns control to thread each 'tick' ms to avoid blocking. */\nexport async function asyncLoop(\n iters: number,\n tick: number,\n cb: (i: number) => void\n): Promise<void> {\n let ts = Date.now();\n for (let i = 0; i < iters; i++) {\n cb(i);\n // Date.now() is not monotonic, so in case if clock goes backwards we return return control too\n const diff = Date.now() - ts;\n if (diff >= 0 && diff < tick) continue;\n await nextTick();\n ts += diff;\n }\n}\n\n// Global symbols, but ts doesn't see them: https://github.com/microsoft/TypeScript/issues/31535\ndeclare const TextEncoder: any;\n\n/**\n * Converts string to bytes using UTF8 encoding.\n * Built-in doesn't validate input to be string: we do the check.\n * @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])\n */\nexport function utf8ToBytes(str: string): Uint8Array {\n if (typeof str !== 'string') throw new Error('string expected');\n return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809\n}\n\n/** KDFs can accept string or Uint8Array for user convenience. */\nexport type KDFInput = string | Uint8Array;\n\n/**\n * Helper for KDFs: consumes uint8array or string.\n * When string is passed, does utf8 decoding, using TextDecoder.\n */\nexport function kdfInputToBytes(data: KDFInput, errorTitle = ''): Uint8Array {\n if (typeof data === 'string') return utf8ToBytes(data);\n return abytes(data, undefined, errorTitle);\n}\n\n/** Copies several Uint8Arrays into one. */\nexport function concatBytes(...arrays: Uint8Array[]): Uint8Array {\n let sum = 0;\n for (let i = 0; i < arrays.length; i++) {\n const a = arrays[i];\n abytes(a);\n sum += a.length;\n }\n const res = new Uint8Array(sum);\n for (let i = 0, pad = 0; i < arrays.length; i++) {\n const a = arrays[i];\n res.set(a, pad);\n pad += a.length;\n }\n return res;\n}\n\ntype EmptyObj = {};\n/** Merges default options and passed options. */\nexport function checkOpts<T1 extends EmptyObj, T2 extends EmptyObj>(\n defaults: T1,\n opts?: T2\n): T1 & T2 {\n if (opts !== undefined && {}.toString.call(opts) !== '[object Object]')\n throw new Error('options must be object or undefined');\n const merged = Object.assign(defaults, opts);\n return merged as T1 & T2;\n}\n\n/** Common interface for all hashes. */\nexport interface Hash<T> {\n blockLen: number; // Bytes per block\n outputLen: number; // Bytes in output\n update(buf: Uint8Array): this;\n digestInto(buf: Uint8Array): void;\n digest(): Uint8Array;\n destroy(): void;\n _cloneInto(to?: T): T;\n clone(): T;\n}\n\n/** PseudoRandom (number) Generator */\nexport interface PRG {\n addEntropy(seed: Uint8Array): void;\n randomBytes(length: number): Uint8Array;\n clean(): void;\n}\n\n/**\n * XOF: streaming API to read digest in chunks.\n * Same as 'squeeze' in keccak/k12 and 'seek' in blake3, but more generic name.\n * When hash used in XOF mode it is up to user to call '.destroy' afterwards, since we cannot\n * destroy state, next call can require more bytes.\n */\nexport type HashXOF<T extends Hash<T>> = Hash<T> & {\n xof(bytes: number): Uint8Array; // Read 'bytes' bytes from digest stream\n xofInto(buf: Uint8Array): Uint8Array; // read buf.length bytes from digest stream into buf\n};\n\n/** Hash constructor */\nexport type HasherCons<T, Opts = undefined> = Opts extends undefined ? () => T : (opts?: Opts) => T;\n/** Optional hash params. */\nexport type HashInfo = {\n oid?: Uint8Array; // DER encoded OID in bytes\n};\n/** Hash function */\nexport type CHash<T extends Hash<T> = Hash<any>, Opts = undefined> = {\n outputLen: number;\n blockLen: number;\n} & HashInfo &\n (Opts extends undefined\n ? {\n (msg: Uint8Array): Uint8Array;\n create(): T;\n }\n : {\n (msg: Uint8Array, opts?: Opts): Uint8Array;\n create(opts?: Opts): T;\n });\n/** XOF with output */\nexport type CHashXOF<T extends HashXOF<T> = HashXOF<any>, Opts = undefined> = CHash<T, Opts>;\n\n/** Creates function with outputLen, blockLen, create properties from a class constructor. */\nexport function createHasher<T extends Hash<T>, Opts = undefined>(\n hashCons: HasherCons<T, Opts>,\n info: HashInfo = {}\n): CHash<T, Opts> {\n const hashC: any = (msg: Uint8Array, opts?: Opts) => hashCons(opts).update(msg).digest();\n const tmp = hashCons(undefined);\n hashC.outputLen = tmp.outputLen;\n hashC.blockLen = tmp.blockLen;\n hashC.create = (opts?: Opts) => hashCons(opts);\n Object.assign(hashC, info);\n return Object.freeze(hashC);\n}\n\n/** Cryptographically secure PRNG. Uses internal OS-level `crypto.getRandomValues`. */\nexport function randomBytes(bytesLength = 32): Uint8Array {\n const cr = typeof globalThis === 'object' ? (globalThis as any).crypto : null;\n if (typeof cr?.getRandomValues !== 'function')\n throw new Error('crypto.getRandomValues must be defined');\n return cr.getRandomValues(new Uint8Array(bytesLength));\n}\n\n/** Creates OID opts for NIST hashes, with prefix 06 09 60 86 48 01 65 03 04 02. */\nexport const oidNist = (suffix: number): Required<HashInfo> => ({\n oid: Uint8Array.from([0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, suffix]),\n});\n","/**\n * HMAC: RFC2104 message authentication code.\n * @module\n */\nimport { abytes, aexists, ahash, clean, type CHash, type Hash } from './utils.ts';\n\n/** Internal class for HMAC. */\nexport class _HMAC<T extends Hash<T>> implements Hash<_HMAC<T>> {\n oHash: T;\n iHash: T;\n blockLen: number;\n outputLen: number;\n private finished = false;\n private destroyed = false;\n\n constructor(hash: CHash, key: Uint8Array) {\n ahash(hash);\n abytes(key, undefined, 'key');\n this.iHash = hash.create() as T;\n if (typeof this.iHash.update !== 'function')\n throw new Error('Expected instance of class which extends utils.Hash');\n this.blockLen = this.iHash.blockLen;\n this.outputLen = this.iHash.outputLen;\n const blockLen = this.blockLen;\n const pad = new Uint8Array(blockLen);\n // blockLen can be bigger than outputLen\n pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);\n for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36;\n this.iHash.update(pad);\n // By doing update (processing of first block) of outer hash here we can re-use it between multiple calls via clone\n this.oHash = hash.create() as T;\n // Undo internal XOR && apply outer XOR\n for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36 ^ 0x5c;\n this.oHash.update(pad);\n clean(pad);\n }\n update(buf: Uint8Array): this {\n aexists(this);\n this.iHash.update(buf);\n return this;\n }\n digestInto(out: Uint8Array): void {\n aexists(this);\n abytes(out, this.outputLen, 'output');\n this.finished = true;\n this.iHash.digestInto(out);\n this.oHash.update(out);\n this.oHash.digestInto(out);\n this.destroy();\n }\n digest(): Uint8Array {\n const out = new Uint8Array(this.oHash.outputLen);\n this.digestInto(out);\n return out;\n }\n _cloneInto(to?: _HMAC<T>): _HMAC<T> {\n // Create new instance without calling constructor since key already in state and we don't know it.\n to ||= Object.create(Object.getPrototypeOf(this), {});\n const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;\n to = to as this;\n to.finished = finished;\n to.destroyed = destroyed;\n to.blockLen = blockLen;\n to.outputLen = outputLen;\n to.oHash = oHash._cloneInto(to.oHash);\n to.iHash = iHash._cloneInto(to.iHash);\n return to;\n }\n clone(): _HMAC<T> {\n return this._cloneInto();\n }\n destroy(): void {\n this.destroyed = true;\n this.oHash.destroy();\n this.iHash.destroy();\n }\n}\n\n/**\n * HMAC: RFC2104 message authentication code.\n * @param hash - function that would be used e.g. sha256\n * @param key - message key\n * @param message - message data\n * @example\n * import { hmac } from '@noble/hashes/hmac';\n * import { sha256 } from '@noble/hashes/sha2';\n * const mac1 = hmac(sha256, 'key', 'message');\n */\nexport const hmac: {\n (hash: CHash, key: Uint8Array, message: Uint8Array): Uint8Array;\n create(hash: CHash, key: Uint8Array): _HMAC<any>;\n} = (hash: CHash, key: Uint8Array, message: Uint8Array): Uint8Array =>\n new _HMAC<any>(hash, key).update(message).digest();\nhmac.create = (hash: CHash, key: Uint8Array) => new _HMAC<any>(hash, key);\n","/**\n * HKDF (RFC 5869): extract + expand in one step.\n * See https://soatok.blog/2021/11/17/understanding-hkdf/.\n * @module\n */\nimport { hmac } from './hmac.ts';\nimport { abytes, ahash, anumber, type CHash, clean } from './utils.ts';\n\n/**\n * HKDF-extract from spec. Less important part. `HKDF-Extract(IKM, salt) -> PRK`\n * Arguments position differs from spec (IKM is first one, since it is not optional)\n * @param hash - hash function that would be used (e.g. sha256)\n * @param ikm - input keying material, the initial key\n * @param salt - optional salt value (a non-secret random value)\n */\nexport function extract(hash: CHash, ikm: Uint8Array, salt?: Uint8Array): Uint8Array {\n ahash(hash);\n // NOTE: some libraries treat zero-length array as 'not provided';\n // we don't, since we have undefined as 'not provided'\n // https://github.com/RustCrypto/KDFs/issues/15\n if (salt === undefined) salt = new Uint8Array(hash.outputLen);\n return hmac(hash, salt, ikm);\n}\n\nconst HKDF_COUNTER = /* @__PURE__ */ Uint8Array.of(0);\nconst EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();\n\n/**\n * HKDF-expand from the spec. The most important part. `HKDF-Expand(PRK, info, L) -> OKM`\n * @param hash - hash function that would be used (e.g. sha256)\n * @param prk - a pseudorandom key of at least HashLen octets (usually, the output from the extract step)\n * @param info - optional context and application specific information (can be a zero-length string)\n * @param length - length of output keying material in bytes\n */\nexport function expand(\n hash: CHash,\n prk: Uint8Array,\n info?: Uint8Array,\n length: number = 32\n): Uint8Array {\n ahash(hash);\n anumber(length, 'length');\n const olen = hash.outputLen;\n if (length > 255 * olen) throw new Error('Length must be <= 255*HashLen');\n const blocks = Math.ceil(length / olen);\n if (info === undefined) info = EMPTY_BUFFER;\n else abytes(info, undefined, 'info');\n // first L(ength) octets of T\n const okm = new Uint8Array(blocks * olen);\n // Re-use HMAC instance between blocks\n const HMAC = hmac.create(hash, prk);\n const HMACTmp = HMAC._cloneInto();\n const T = new Uint8Array(HMAC.outputLen);\n for (let counter = 0; counter < blocks; counter++) {\n HKDF_COUNTER[0] = counter + 1;\n // T(0) = empty string (zero length)\n // T(N) = HMAC-Hash(PRK, T(N-1) | info | N)\n HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T)\n .update(info)\n .update(HKDF_COUNTER)\n .digestInto(T);\n okm.set(T, olen * counter);\n HMAC._cloneInto(HMACTmp);\n }\n HMAC.destroy();\n HMACTmp.destroy();\n clean(T, HKDF_COUNTER);\n return okm.slice(0, length);\n}\n\n/**\n * HKDF (RFC 5869): derive keys from an initial input.\n * Combines hkdf_extract + hkdf_expand in one step\n * @param hash - hash function that would be used (e.g. sha256)\n * @param ikm - input keying material, the initial key\n * @param salt - optional salt value (a non-secret random value)\n * @param info - optional context and application specific information (can be a zero-length string)\n * @param length - length of output keying material in bytes\n * @example\n * import { hkdf } from '@noble/hashes/hkdf';\n * import { sha256 } from '@noble/hashes/sha2';\n * import { randomBytes } from '@noble/hashes/utils';\n * const inputKey = randomBytes(32);\n * const salt = randomBytes(32);\n * const info = 'application-key';\n * const hk1 = hkdf(sha256, inputKey, salt, info, 32);\n */\nexport const hkdf = (\n hash: CHash,\n ikm: Uint8Array,\n salt: Uint8Array | undefined,\n info: Uint8Array | undefined,\n length: number\n): Uint8Array => expand(hash, extract(hash, ikm, salt), info, length);\n","/**\n * Internal Merkle-Damgard hash utils.\n * @module\n */\nimport { abytes, aexists, aoutput, clean, createView, type Hash } from './utils.ts';\n\n/** Choice: a ? b : c */\nexport function Chi(a: number, b: number, c: number): number {\n return (a & b) ^ (~a & c);\n}\n\n/** Majority function, true if any two inputs is true. */\nexport function Maj(a: number, b: number, c: number): number {\n return (a & b) ^ (a & c) ^ (b & c);\n}\n\n/**\n * Merkle-Damgard hash construction base class.\n * Could be used to create MD5, RIPEMD, SHA1, SHA2.\n */\nexport abstract class HashMD<T extends HashMD<T>> implements Hash<T> {\n protected abstract process(buf: DataView, offset: number): void;\n protected abstract get(): number[];\n protected abstract set(...args: number[]): void;\n abstract destroy(): void;\n protected abstract roundClean(): void;\n\n readonly blockLen: number;\n readonly outputLen: number;\n readonly padOffset: number;\n readonly isLE: boolean;\n\n // For partial updates less than block size\n protected buffer: Uint8Array;\n protected view: DataView;\n protected finished = false;\n protected length = 0;\n protected pos = 0;\n protected destroyed = false;\n\n constructor(blockLen: number, outputLen: number, padOffset: number, isLE: boolean) {\n this.blockLen = blockLen;\n this.outputLen = outputLen;\n this.padOffset = padOffset;\n this.isLE = isLE;\n this.buffer = new Uint8Array(blockLen);\n this.view = createView(this.buffer);\n }\n update(data: Uint8Array): this {\n aexists(this);\n abytes(data);\n const { view, buffer, blockLen } = this;\n const len = data.length;\n for (let pos = 0; pos < len; ) {\n const take = Math.min(blockLen - this.pos, len - pos);\n // Fast path: we have at least one block in input, cast it to view and process\n if (take === blockLen) {\n const dataView = createView(data);\n for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);\n continue;\n }\n buffer.set(data.subarray(pos, pos + take), this.pos);\n this.pos += take;\n pos += take;\n if (this.pos === blockLen) {\n this.process(view, 0);\n this.pos = 0;\n }\n }\n this.length += data.length;\n this.roundClean();\n return this;\n }\n digestInto(out: Uint8Array): void {\n aexists(this);\n aoutput(out, this);\n this.finished = true;\n // Padding\n // We can avoid allocation of buffer for padding completely if it\n // was previously not allocated here. But it won't change performance.\n const { buffer, view, blockLen, isLE } = this;\n let { pos } = this;\n // append the bit '1' to the message\n buffer[pos++] = 0b10000000;\n clean(this.buffer.subarray(pos));\n // we have less than padOffset left in buffer, so we cannot put length in\n // current block, need process it and pad again\n if (this.padOffset > blockLen - pos) {\n this.process(view, 0);\n pos = 0;\n }\n // Pad until full block byte with zeros\n for (let i = pos; i < blockLen; i++) buffer[i] = 0;\n // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that\n // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.\n // So we just write lowest 64 bits of that value.\n view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);\n this.process(view, 0);\n const oview = createView(out);\n const len = this.outputLen;\n // NOTE: we do division by 4 later, which must be fused in single op with modulo by JIT\n if (len % 4) throw new Error('_sha2: outputLen must be aligned to 32bit');\n const outLen = len / 4;\n const state = this.get();\n if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state');\n for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);\n }\n digest(): Uint8Array {\n const { buffer, outputLen } = this;\n this.digestInto(buffer);\n const res = buffer.slice(0, outputLen);\n this.destroy();\n return res;\n }\n _cloneInto(to?: T): T {\n to ||= new (this.constructor as any)() as T;\n to.set(...this.get());\n const { blockLen, buffer, length, finished, destroyed, pos } = this;\n to.destroyed = destroyed;\n to.finished = finished;\n to.length = length;\n to.pos = pos;\n if (length % blockLen) to.buffer.set(buffer);\n return to as unknown as any;\n }\n clone(): T {\n return this._cloneInto();\n }\n}\n\n/**\n * Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.\n * Check out `test/misc/sha2-gen-iv.js` for recomputation guide.\n */\n\n/** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */\nexport const SHA256_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,\n]);\n\n/** Initial SHA224 state. Bits 32..64 of frac part of sqrt of primes 23..53 */\nexport const SHA224_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([\n 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4,\n]);\n\n/** Initial SHA384 state. Bits 0..64 of frac part of sqrt of primes 23..53 */\nexport const SHA384_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([\n 0xcbbb9d5d, 0xc1059ed8, 0x629a292a, 0x367cd507, 0x9159015a, 0x3070dd17, 0x152fecd8, 0xf70e5939,\n 0x67332667, 0xffc00b31, 0x8eb44a87, 0x68581511, 0xdb0c2e0d, 0x64f98fa7, 0x47b5481d, 0xbefa4fa4,\n]);\n\n/** Initial SHA512 state. Bits 0..64 of frac part of sqrt of primes 2..19 */\nexport const SHA512_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([\n 0x6a09e667, 0xf3bcc908, 0xbb67ae85, 0x84caa73b, 0x3c6ef372, 0xfe94f82b, 0xa54ff53a, 0x5f1d36f1,\n 0x510e527f, 0xade682d1, 0x9b05688c, 0x2b3e6c1f, 0x1f83d9ab, 0xfb41bd6b, 0x5be0cd19, 0x137e2179,\n]);\n","/**\n * SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.\n * SHA256 is the fastest hash implementable in JS, even faster than Blake3.\n * Check out [RFC 4634](https://www.rfc-editor.org/rfc/rfc4634) and\n * [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).\n * @module\n */\nimport { Chi, HashMD, Maj, SHA224_IV, SHA256_IV, SHA384_IV, SHA512_IV } from './_md.ts';\nimport * as u64 from './_u64.ts';\nimport { type CHash, clean, createHasher, oidNist, rotr } from './utils.ts';\n\n/**\n * Round constants:\n * First 32 bits of fractional parts of the cube roots of the first 64 primes 2..311)\n */\n// prettier-ignore\nconst SHA256_K = /* @__PURE__ */ Uint32Array.from([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n]);\n\n/** Reusable temporary buffer. \"W\" comes straight from spec. */\nconst SHA256_W = /* @__PURE__ */ new Uint32Array(64);\n\n/** Internal 32-byte base SHA2 hash class. */\nabstract class SHA2_32B<T extends SHA2_32B<T>> extends HashMD<T> {\n // We cannot use array here since array allows indexing by variable\n // which means optimizer/compiler cannot use registers.\n protected abstract A: number;\n protected abstract B: number;\n protected abstract C: number;\n protected abstract D: number;\n protected abstract E: number;\n protected abstract F: number;\n protected abstract G: number;\n protected abstract H: number;\n\n constructor(outputLen: number) {\n super(64, outputLen, 8, false);\n }\n protected get(): [number, number, number, number, number, number, number, number] {\n const { A, B, C, D, E, F, G, H } = this;\n return [A, B, C, D, E, F, G, H];\n }\n // prettier-ignore\n protected set(\n A: number, B: number, C: number, D: number, E: number, F: number, G: number, H: number\n ): void {\n this.A = A | 0;\n this.B = B | 0;\n this.C = C | 0;\n this.D = D | 0;\n this.E = E | 0;\n this.F = F | 0;\n this.G = G | 0;\n this.H = H | 0;\n }\n protected process(view: DataView, offset: number): void {\n // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array\n for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false);\n for (let i = 16; i < 64; i++) {\n const W15 = SHA256_W[i - 15];\n const W2 = SHA256_W[i - 2];\n const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);\n const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);\n SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;\n }\n // Compression function main loop, 64 rounds\n let { A, B, C, D, E, F, G, H } = this;\n for (let i = 0; i < 64; i++) {\n const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);\n const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;\n const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);\n const T2 = (sigma0 + Maj(A, B, C)) | 0;\n H = G;\n G = F;\n F = E;\n E = (D + T1) | 0;\n D = C;\n C = B;\n B = A;\n A = (T1 + T2) | 0;\n }\n // Add the compressed chunk to the current hash value\n A = (A + this.A) | 0;\n B = (B + this.B) | 0;\n C = (C + this.C) | 0;\n D = (D + this.D) | 0;\n E = (E + this.E) | 0;\n F = (F + this.F) | 0;\n G = (G + this.G) | 0;\n H = (H + this.H) | 0;\n this.set(A, B, C, D, E, F, G, H);\n }\n protected roundClean(): void {\n clean(SHA256_W);\n }\n destroy(): void {\n this.set(0, 0, 0, 0, 0, 0, 0, 0);\n clean(this.buffer);\n }\n}\n\n/** Internal SHA2-256 hash class. */\nexport class _SHA256 extends SHA2_32B<_SHA256> {\n // We cannot use array here since array allows indexing by variable\n // which means optimizer/compiler cannot use registers.\n protected A: number = SHA256_IV[0] | 0;\n protected B: number = SHA256_IV[1] | 0;\n protected C: number = SHA256_IV[2] | 0;\n protected D: number = SHA256_IV[3] | 0;\n protected E: number = SHA256_IV[4] | 0;\n protected F: number = SHA256_IV[5] | 0;\n protected G: number = SHA256_IV[6] | 0;\n protected H: number = SHA256_IV[7] | 0;\n constructor() {\n super(32);\n }\n}\n\n/** Internal SHA2-224 hash class. */\nexport class _SHA224 extends SHA2_32B<_SHA224> {\n protected A: number = SHA224_IV[0] | 0;\n protected B: number = SHA224_IV[1] | 0;\n protected C: number = SHA224_IV[2] | 0;\n protected D: number = SHA224_IV[3] | 0;\n protected E: number = SHA224_IV[4] | 0;\n protected F: number = SHA224_IV[5] | 0;\n protected G: number = SHA224_IV[6] | 0;\n protected H: number = SHA224_IV[7] | 0;\n constructor() {\n super(28);\n }\n}\n\n// SHA2-512 is slower than sha256 in js because u64 operations are slow.\n\n// Round contants\n// First 32 bits of the fractional parts of the cube roots of the first 80 primes 2..409\n// prettier-ignore\nconst K512 = /* @__PURE__ */ (() => u64.split([\n '0x428a2f98d728ae22', '0x7137449123ef65cd', '0xb5c0fbcfec4d3b2f', '0xe9b5dba58189dbbc',\n '0x3956c25bf348b538', '0x59f111f1b605d019', '0x923f82a4af194f9b', '0xab1c5ed5da6d8118',\n '0xd807aa98a3030242', '0x12835b0145706fbe', '0x243185be4ee4b28c', '0x550c7dc3d5ffb4e2',\n '0x72be5d74f27b896f', '0x80deb1fe3b1696b1', '0x9bdc06a725c71235', '0xc19bf174cf692694',\n '0xe49b69c19ef14ad2', '0xefbe4786384f25e3', '0x0fc19dc68b8cd5b5', '0x240ca1cc77ac9c65',\n '0x2de92c6f592b0275', '0x4a7484aa6ea6e483', '0x5cb0a9dcbd41fbd4', '0x76f988da831153b5',\n '0x983e5152ee66dfab', '0xa831c66d2db43210', '0xb00327c898fb213f', '0xbf597fc7beef0ee4',\n '0xc6e00bf33da88fc2', '0xd5a79147930aa725', '0x06ca6351e003826f', '0x142929670a0e6e70',\n '0x27b70a8546d22ffc', '0x2e1b21385c26c926', '0x4d2c6dfc5ac42aed', '0x53380d139d95b3df',\n '0x650a73548baf63de', '0x766a0abb3c77b2a8', '0x81c2c92e47edaee6', '0x92722c851482353b',\n '0xa2bfe8a14cf10364', '0xa81a664bbc423001', '0xc24b8b70d0f89791', '0xc76c51a30654be30',\n '0xd192e819d6ef5218', '0xd69906245565a910', '0xf40e35855771202a', '0x106aa07032bbd1b8',\n '0x19a4c116b8d2d0c8', '0x1e376c085141ab53', '0x2748774cdf8eeb99', '0x34b0bcb5e19b48a8',\n '0x391c0cb3c5c95a63', '0x4ed8aa4ae3418acb', '0x5b9cca4f7763e373', '0x682e6ff3d6b2b8a3',\n '0x748f82ee5defb2fc', '0x78a5636f43172f60', '0x84c87814a1f0ab72', '0x8cc702081a6439ec',\n '0x90befffa23631e28', '0xa4506cebde82bde9', '0xbef9a3f7b2c67915', '0xc67178f2e372532b',\n '0xca273eceea26619c', '0xd186b8c721c0c207', '0xeada7dd6cde0eb1e', '0xf57d4f7fee6ed178',\n '0x06f067aa72176fba', '0x0a637dc5a2c898a6', '0x113f9804bef90dae', '0x1b710b35131c471b',\n '0x28db77f523047d84', '0x32caab7b40c72493', '0x3c9ebe0a15c9bebc', '0x431d67c49c100d4c',\n '0x4cc5d4becb3e42b6', '0x597f299cfc657e2a', '0x5fcb6fab3ad6faec', '0x6c44198c4a475817'\n].map(n => BigInt(n))))();\nconst SHA512_Kh = /* @__PURE__ */ (() => K512[0])();\nconst SHA512_Kl = /* @__PURE__ */ (() => K512[1])();\n\n// Reusable temporary buffers\nconst SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);\nconst SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);\n\n/** Internal 64-byte base SHA2 hash class. */\nabstract class SHA2_64B<T extends SHA2_64B<T>> extends HashMD<T> {\n // We cannot use array here since array allows indexing by variable\n // which means optimizer/compiler cannot use registers.\n // h -- high 32 bits, l -- low 32 bits\n protected abstract Ah: number;\n protected abstract Al: number;\n protected abstract Bh: number;\n protected abstract Bl: number;\n protected abstract Ch: number;\n protected abstract Cl: number;\n protected abstract Dh: number;\n protected abstract Dl: number;\n protected abstract Eh: number;\n protected abstract El: number;\n protected abstract Fh: number;\n protected abstract Fl: number;\n protected abstract Gh: number;\n protected abstract Gl: number;\n protected abstract Hh: number;\n protected abstract Hl: number;\n\n constructor(outputLen: number) {\n super(128, outputLen, 16, false);\n }\n // prettier-ignore\n protected get(): [\n number, number, number, number, number, number, number, number,\n number, number, number, number, number, number, number, number\n ] {\n const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;\n return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];\n }\n // prettier-ignore\n protected set(\n Ah: number, Al: number, Bh: number, Bl: number, Ch: number, Cl: number, Dh: number, Dl: number,\n Eh: number, El: number, Fh: number, Fl: number, Gh: number, Gl: number, Hh: number, Hl: number\n ): void {\n this.Ah = Ah | 0;\n this.Al = Al | 0;\n this.Bh = Bh | 0;\n this.Bl = Bl | 0;\n this.Ch = Ch | 0;\n this.Cl = Cl | 0;\n this.Dh = Dh | 0;\n this.Dl = Dl | 0;\n this.Eh = Eh | 0;\n this.El = El | 0;\n this.Fh = Fh | 0;\n this.Fl = Fl | 0;\n this.Gh = Gh | 0;\n this.Gl = Gl | 0;\n this.Hh = Hh | 0;\n this.Hl = Hl | 0;\n }\n protected process(view: DataView, offset: number): void {\n // Extend the first 16 words into the remaining 64 words w[16..79] of the message schedule array\n for (let i = 0; i < 16; i++, offset += 4) {\n SHA512_W_H[i] = view.getUint32(offset);\n SHA512_W_L[i] = view.getUint32((offset += 4));\n }\n for (let i = 16; i < 80; i++) {\n // s0 := (w[i-15] rightrotate 1) xor (w[i-15] rightrotate 8) xor (w[i-15] rightshift 7)\n const W15h = SHA512_W_H[i - 15] | 0;\n const W15l = SHA512_W_L[i - 15] | 0;\n const s0h = u64.rotrSH(W15h, W15l, 1) ^ u64.rotrSH(W15h, W15l, 8) ^ u64.shrSH(W15h, W15l, 7);\n const s0l = u64.rotrSL(W15h, W15l, 1) ^ u64.rotrSL(W15h, W15l, 8) ^ u64.shrSL(W15h, W15l, 7);\n // s1 := (w[i-2] rightrotate 19) xor (w[i-2] rightrotate 61) xor (w[i-2] rightshift 6)\n const W2h = SHA512_W_H[i - 2] | 0;\n const W2l = SHA512_W_L[i - 2] | 0;\n const s1h = u64.rotrSH(W2h, W2l, 19) ^ u64.rotrBH(W2h, W2l, 61) ^ u64.shrSH(W2h, W2l, 6);\n const s1l = u64.rotrSL(W2h, W2l, 19) ^ u64.rotrBL(W2h, W2l, 61) ^ u64.shrSL(W2h, W2l, 6);\n // SHA256_W[i] = s0 + s1 + SHA256_W[i - 7] + SHA256_W[i - 16];\n const SUMl = u64.add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);\n const SUMh = u64.add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);\n SHA512_W_H[i] = SUMh | 0;\n SHA512_W_L[i] = SUMl | 0;\n }\n let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;\n // Compression function main loop, 80 rounds\n for (let i = 0; i < 80; i++) {\n // S1 := (e rightrotate 14) xor (e rightrotate 18) xor (e rightrotate 41)\n const sigma1h = u64.rotrSH(Eh, El, 14) ^ u64.rotrSH(Eh, El, 18) ^ u64.rotrBH(Eh, El, 41);\n const sigma1l = u64.rotrSL(Eh, El, 14) ^ u64.rotrSL(Eh, El, 18) ^ u64.rotrBL(Eh, El, 41);\n //const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;\n const CHIh = (Eh & Fh) ^ (~Eh & Gh);\n const CHIl = (El & Fl) ^ (~El & Gl);\n // T1 = H + sigma1 + Chi(E, F, G) + SHA512_K[i] + SHA512_W[i]\n // prettier-ignore\n const T1ll = u64.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);\n const T1h = u64.add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);\n const T1l = T1ll | 0;\n // S0 := (a rightrotate 28) xor (a rightrotate 34) xor (a rightrotate 39)\n const sigma0h = u64.rotrSH(Ah, Al, 28) ^ u64.rotrBH(Ah, Al, 34) ^ u64.rotrBH(Ah, Al, 39);\n const sigma0l = u64.rotrSL(Ah, Al, 28) ^ u64.rotrBL(Ah, Al, 34) ^ u64.rotrBL(Ah, Al, 39);\n const MAJh = (Ah & Bh) ^ (Ah & Ch) ^ (Bh & Ch);\n const MAJl = (Al & Bl) ^ (Al & Cl) ^ (Bl & Cl);\n Hh = Gh | 0;\n Hl = Gl | 0;\n Gh = Fh | 0;\n Gl = Fl | 0;\n Fh = Eh | 0;\n Fl = El | 0;\n ({ h: Eh, l: El } = u64.add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));\n Dh = Ch | 0;\n Dl = Cl | 0;\n Ch = Bh | 0;\n Cl = Bl | 0;\n Bh = Ah | 0;\n Bl = Al | 0;\n const All = u64.add3L(T1l, sigma0l, MAJl);\n Ah = u64.add3H(All, T1h, sigma0h, MAJh);\n Al = All | 0;\n }\n // Add the compressed chunk to the current hash value\n ({ h: Ah, l: Al } = u64.add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));\n ({ h: Bh, l: Bl } = u64.add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));\n ({ h: Ch, l: Cl } = u64.add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));\n ({ h: Dh, l: Dl } = u64.add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));\n ({ h: Eh, l: El } = u64.add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));\n ({ h: Fh, l: Fl } = u64.add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));\n ({ h: Gh, l: Gl } = u64.add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));\n ({ h: Hh, l: Hl } = u64.add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));\n this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);\n }\n protected roundClean(): void {\n clean(SHA512_W_H, SHA512_W_L);\n }\n destroy(): void {\n clean(this.buffer);\n this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n }\n}\n\n/** Internal SHA2-512 hash class. */\nexport class _SHA512 extends SHA2_64B<_SHA512> {\n protected Ah: number = SHA512_IV[0] | 0;\n protected Al: number = SHA512_IV[1] | 0;\n protected Bh: number = SHA512_IV[2] | 0;\n protected Bl: number = SHA512_IV[3] | 0;\n protected Ch: number = SHA512_IV[4] | 0;\n protected Cl: number = SHA512_IV[5] | 0;\n protected Dh: number = SHA512_IV[6] | 0;\n protected Dl: number = SHA512_IV[7] | 0;\n protected Eh: number = SHA512_IV[8] | 0;\n protected El: number = SHA512_IV[9] | 0;\n protected Fh: number = SHA512_IV[10] | 0;\n protected Fl: number = SHA512_IV[11] | 0;\n protected Gh: number = SHA512_IV[12] | 0;\n protected Gl: number = SHA512_IV[13] | 0;\n protected Hh: number = SHA512_IV[14] | 0;\n protected Hl: number = SHA512_IV[15] | 0;\n\n constructor() {\n super(64);\n }\n}\n\n/** Internal SHA2-384 hash class. */\nexport class _SHA384 extends SHA2_64B<_SHA384> {\n protected Ah: number = SHA384_IV[0] | 0;\n protected Al: number = SHA384_IV[1] | 0;\n protected Bh: number = SHA384_IV[2] | 0;\n protected Bl: number = SHA384_IV[3] | 0;\n protected Ch: number = SHA384_IV[4] | 0;\n protected Cl: number = SHA384_IV[5] | 0;\n protected Dh: number = SHA384_IV[6] | 0;\n protected Dl: number = SHA384_IV[7] | 0;\n protected Eh: number = SHA384_IV[8] | 0;\n protected El: number = SHA384_IV[9] | 0;\n protected Fh: number = SHA384_IV[10] | 0;\n protected Fl: number = SHA384_IV[11] | 0;\n protected Gh: number = SHA384_IV[12] | 0;\n protected Gl: number = SHA384_IV[13] | 0;\n protected Hh: number = SHA384_IV[14] | 0;\n protected Hl: number = SHA384_IV[15] | 0;\n\n constructor() {\n super(48);\n }\n}\n\n/**\n * Truncated SHA512/256 and SHA512/224.\n * SHA512_IV is XORed with 0xa5a5a5a5a5a5a5a5, then used as \"intermediary\" IV of SHA512/t.\n * Then t hashes string to produce result IV.\n * See `test/misc/sha2-gen-iv.js`.\n */\n\n/** SHA512/224 IV */\nconst T224_IV = /* @__PURE__ */ Uint32Array.from([\n 0x8c3d37c8, 0x19544da2, 0x73e19966, 0x89dcd4d6, 0x1dfab7ae, 0x32ff9c82, 0x679dd514, 0x582f9fcf,\n 0x0f6d2b69, 0x7bd44da8, 0x77e36f73, 0x04c48942, 0x3f9d85a8, 0x6a1d36c8, 0x1112e6ad, 0x91d692a1,\n]);\n\n/** SHA512/256 IV */\nconst T256_IV = /* @__PURE__ */ Uint32Array.from([\n 0x22312194, 0xfc2bf72c, 0x9f555fa3, 0xc84c64c2, 0x2393b86b, 0x6f53b151, 0x96387719, 0x5940eabd,\n 0x96283ee2, 0xa88effe3, 0xbe5e1e25, 0x53863992, 0x2b0199fc, 0x2c85b8aa, 0x0eb72ddc, 0x81c52ca2,\n]);\n\n/** Internal SHA2-512/224 hash class. */\nexport class _SHA512_224 extends SHA2_64B<_SHA512_224> {\n protected Ah: number = T224_IV[0] | 0;\n protected Al: number = T224_IV[1] | 0;\n protected Bh: number = T224_IV[2] | 0;\n protected Bl: number = T224_IV[3] | 0;\n protected Ch: number = T224_IV[4] | 0;\n protected Cl: number = T224_IV[5] | 0;\n protected Dh: number = T224_IV[6] | 0;\n protected Dl: number = T224_IV[7] | 0;\n protected Eh: number = T224_IV[8] | 0;\n protected El: number = T224_IV[9] | 0;\n protected Fh: number = T224_IV[10] | 0;\n protected Fl: number = T224_IV[11] | 0;\n protected Gh: number = T224_IV[12] | 0;\n protected Gl: number = T224_IV[13] | 0;\n protected Hh: number = T224_IV[14] | 0;\n protected Hl: number = T224_IV[15] | 0;\n\n constructor() {\n super(28);\n }\n}\n\n/** Internal SHA2-512/256 hash class. */\nexport class _SHA512_256 extends SHA2_64B<_SHA512_256> {\n protected Ah: number = T256_IV[0] | 0;\n protected Al: number = T256_IV[1] | 0;\n protected Bh: number = T256_IV[2] | 0;\n protected Bl: number = T256_IV[3] | 0;\n protected Ch: number = T256_IV[4] | 0;\n protected Cl: number = T256_IV[5] | 0;\n protected Dh: number = T256_IV[6] | 0;\n protected Dl: number = T256_IV[7] | 0;\n protected Eh: number = T256_IV[8] | 0;\n protected El: number = T256_IV[9] | 0;\n protected Fh: number = T256_IV[10] | 0;\n protected Fl: number = T256_IV[11] | 0;\n protected Gh: number = T256_IV[12] | 0;\n protected Gl: number = T256_IV[13] | 0;\n protected Hh: number = T256_IV[14] | 0;\n protected Hl: number = T256_IV[15] | 0;\n\n constructor() {\n super(32);\n }\n}\n\n/**\n * SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info:\n *\n * - Trying 2^128 hashes would get 50% chance of collision, using birthday attack.\n * - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.\n * - Each sha256 hash is executing 2^18 bit operations.\n * - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.\n */\nexport const sha256: CHash<_SHA256> = /* @__PURE__ */ createHasher(\n () => new _SHA256(),\n /* @__PURE__ */ oidNist(0x01)\n);\n/** SHA2-224 hash function from RFC 4634 */\nexport const sha224: CHash<_SHA224> = /* @__PURE__ */ createHasher(\n () => new _SHA224(),\n /* @__PURE__ */ oidNist(0x04)\n);\n\n/** SHA2-512 hash function from RFC 4634. */\nexport const sha512: CHash<_SHA512> = /* @__PURE__ */ createHasher(\n () => new _SHA512(),\n /* @__PURE__ */ oidNist(0x03)\n);\n/** SHA2-384 hash function from RFC 4634. */\nexport const sha384: CHash<_SHA384> = /* @__PURE__ */ createHasher(\n () => new _SHA384(),\n /* @__PURE__ */ oidNist(0x02)\n);\n\n/**\n * SHA2-512/256 \"truncated\" hash function, with improved resistance to length extension attacks.\n * See the paper on [truncated SHA512](https://eprint.iacr.org/2010/548.pdf).\n */\nexport const sha512_256: CHash<_SHA512_256> = /* @__PURE__ */ createHasher(\n () => new _SHA512_256(),\n /* @__PURE__ */ oidNist(0x06)\n);\n/**\n * SHA2-512/224 \"truncated\" hash function, with improved resistance to length extension attacks.\n * See the paper on [truncated SHA512](https://eprint.iacr.org/2010/548.pdf).\n */\nexport const sha512_224: CHash<_SHA512_224> = /* @__PURE__ */ createHasher(\n () => new _SHA512_224(),\n /* @__PURE__ */ oidNist(0x05)\n);\n","/**\n * Cryptographic utilities for SDK2\n *\n * Provides BIP39 mnemonic and BIP32 key derivation functions.\n * Platform-independent - no browser-specific APIs.\n */\n\nimport * as bip39 from 'bip39';\nimport CryptoJS from 'crypto-js';\nimport elliptic from 'elliptic';\nimport { encodeBech32 } from './bech32';\nimport { SphereError } from './errors';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst ec = new elliptic.ec('secp256k1');\n\n/** secp256k1 curve order */\nconst CURVE_ORDER = BigInt(\n '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'\n);\n\n/** Default derivation path for Unicity (BIP44) */\nexport const DEFAULT_DERIVATION_PATH = \"m/44'/0'/0'\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface MasterKey {\n privateKey: string;\n chainCode: string;\n}\n\nexport interface DerivedKey {\n privateKey: string;\n chainCode: string;\n}\n\nexport interface KeyPair {\n privateKey: string;\n publicKey: string;\n}\n\nexport interface AddressInfo extends KeyPair {\n address: string;\n path: string;\n index: number;\n}\n\n// =============================================================================\n// BIP39 Mnemonic Functions\n// =============================================================================\n\n/**\n * Generate a new BIP39 mnemonic phrase\n * @param strength - Entropy bits (128 = 12 words, 256 = 24 words)\n */\nexport function generateMnemonic(strength: 128 | 256 = 128): string {\n return bip39.generateMnemonic(strength);\n}\n\n/**\n * Validate a BIP39 mnemonic phrase\n */\nexport function validateMnemonic(mnemonic: string): boolean {\n return bip39.validateMnemonic(mnemonic);\n}\n\n/**\n * Convert mnemonic to seed (64-byte hex string)\n * @param mnemonic - BIP39 mnemonic phrase\n * @param passphrase - Optional passphrase for additional security\n */\nexport async function mnemonicToSeed(\n mnemonic: string,\n passphrase: string = ''\n): Promise<string> {\n const seedBuffer = await bip39.mnemonicToSeed(mnemonic, passphrase);\n return Buffer.from(seedBuffer).toString('hex');\n}\n\n/**\n * Synchronous version of mnemonicToSeed\n */\nexport function mnemonicToSeedSync(\n mnemonic: string,\n passphrase: string = ''\n): string {\n const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, passphrase);\n return Buffer.from(seedBuffer).toString('hex');\n}\n\n/**\n * Convert mnemonic to entropy (for recovery purposes)\n */\nexport function mnemonicToEntropy(mnemonic: string): string {\n return bip39.mnemonicToEntropy(mnemonic);\n}\n\n/**\n * Convert entropy to mnemonic\n */\nexport function entropyToMnemonic(entropy: string): string {\n return bip39.entropyToMnemonic(entropy);\n}\n\n// =============================================================================\n// BIP32 Key Derivation\n// =============================================================================\n\n/**\n * Generate master key from seed (BIP32 standard)\n * Uses HMAC-SHA512 with key \"Bitcoin seed\"\n */\nexport function generateMasterKey(seedHex: string): MasterKey {\n const I = CryptoJS.HmacSHA512(\n CryptoJS.enc.Hex.parse(seedHex),\n CryptoJS.enc.Utf8.parse('Bitcoin seed')\n ).toString();\n\n const IL = I.substring(0, 64); // Left 32 bytes - master private key\n const IR = I.substring(64); // Right 32 bytes - master chain code\n\n // Validate master key\n const masterKeyBigInt = BigInt('0x' + IL);\n if (masterKeyBigInt === 0n || masterKeyBigInt >= CURVE_ORDER) {\n throw new SphereError('Invalid master key generated', 'VALIDATION_ERROR');\n }\n\n return {\n privateKey: IL,\n chainCode: IR,\n };\n}\n\n/**\n * Derive child key using BIP32 standard\n * @param parentPrivKey - Parent private key (64 hex chars)\n * @param parentChainCode - Parent chain code (64 hex chars)\n * @param index - Child index (>= 0x80000000 for hardened)\n */\nexport function deriveChildKey(\n parentPrivKey: string,\n parentChainCode: string,\n index: number\n): DerivedKey {\n const isHardened = index >= 0x80000000;\n let data: string;\n\n if (isHardened) {\n // Hardened derivation: 0x00 || parentPrivKey || index\n const indexHex = index.toString(16).padStart(8, '0');\n data = '00' + parentPrivKey + indexHex;\n } else {\n // Non-hardened derivation: compressedPubKey || index\n const keyPair = ec.keyFromPrivate(parentPrivKey, 'hex');\n const compressedPubKey = keyPair.getPublic(true, 'hex');\n const indexHex = index.toString(16).padStart(8, '0');\n data = compressedPubKey + indexHex;\n }\n\n // HMAC-SHA512 with chain code as key\n const I = CryptoJS.HmacSHA512(\n CryptoJS.enc.Hex.parse(data),\n CryptoJS.enc.Hex.parse(parentChainCode)\n ).toString();\n\n const IL = I.substring(0, 64); // Left 32 bytes\n const IR = I.substring(64); // Right 32 bytes (new chain code)\n\n // Add IL to parent key mod n (curve order)\n const ilBigInt = BigInt('0x' + IL);\n const parentKeyBigInt = BigInt('0x' + parentPrivKey);\n\n // Check IL is valid (less than curve order)\n if (ilBigInt >= CURVE_ORDER) {\n throw new SphereError('Invalid key: IL >= curve order', 'VALIDATION_ERROR');\n }\n\n const childKeyBigInt = (ilBigInt + parentKeyBigInt) % CURVE_ORDER;\n\n // Check child key is valid (not zero)\n if (childKeyBigInt === 0n) {\n throw new SphereError('Invalid key: child key is zero', 'VALIDATION_ERROR');\n }\n\n const childPrivKey = childKeyBigInt.toString(16).padStart(64, '0');\n\n return {\n privateKey: childPrivKey,\n chainCode: IR,\n };\n}\n\n/**\n * Derive key at a full BIP32/BIP44 path\n * @param masterPrivKey - Master private key\n * @param masterChainCode - Master chain code\n * @param path - BIP44 path like \"m/44'/0'/0'/0/0\"\n */\nexport function deriveKeyAtPath(\n masterPrivKey: string,\n masterChainCode: string,\n path: string\n): DerivedKey {\n const pathParts = path.replace('m/', '').split('/');\n\n let currentKey = masterPrivKey;\n let currentChainCode = masterChainCode;\n\n for (const part of pathParts) {\n const isHardened = part.endsWith(\"'\") || part.endsWith('h');\n const indexStr = part.replace(/['h]$/, '');\n let index = parseInt(indexStr, 10);\n\n if (isHardened) {\n index += 0x80000000; // Add hardened offset\n }\n\n const derived = deriveChildKey(currentKey, currentChainCode, index);\n currentKey = derived.privateKey;\n currentChainCode = derived.chainCode;\n }\n\n return {\n privateKey: currentKey,\n chainCode: currentChainCode,\n };\n}\n\n// =============================================================================\n// Key Pair Operations\n// =============================================================================\n\n/**\n * Get public key from private key\n * @param privateKey - Private key as hex string\n * @param compressed - Return compressed public key (default: true)\n */\nexport function getPublicKey(privateKey: string, compressed: boolean = true): string {\n const keyPair = ec.keyFromPrivate(privateKey, 'hex');\n return keyPair.getPublic(compressed, 'hex');\n}\n\n/**\n * Create key pair from private key\n */\nexport function createKeyPair(privateKey: string): KeyPair {\n return {\n privateKey,\n publicKey: getPublicKey(privateKey),\n };\n}\n\n// =============================================================================\n// Hash Functions\n// =============================================================================\n\n/**\n * Compute SHA256 hash\n */\nexport function sha256(data: string, inputEncoding: 'hex' | 'utf8' = 'hex'): string {\n const parsed =\n inputEncoding === 'hex'\n ? CryptoJS.enc.Hex.parse(data)\n : CryptoJS.enc.Utf8.parse(data);\n return CryptoJS.SHA256(parsed).toString();\n}\n\n/**\n * Compute RIPEMD160 hash\n */\nexport function ripemd160(data: string, inputEncoding: 'hex' | 'utf8' = 'hex'): string {\n const parsed =\n inputEncoding === 'hex'\n ? CryptoJS.enc.Hex.parse(data)\n : CryptoJS.enc.Utf8.parse(data);\n return CryptoJS.RIPEMD160(parsed).toString();\n}\n\n/**\n * Compute HASH160 (SHA256 -> RIPEMD160)\n */\nexport function hash160(data: string): string {\n const sha = sha256(data, 'hex');\n return ripemd160(sha, 'hex');\n}\n\n/**\n * Compute double SHA256\n */\nexport function doubleSha256(data: string, inputEncoding: 'hex' | 'utf8' = 'hex'): string {\n const first = sha256(data, inputEncoding);\n return sha256(first, 'hex');\n}\n\n/**\n * Alias for hash160 (L1 SDK compatibility)\n */\nexport const computeHash160 = hash160;\n\n/**\n * Convert hex string to Uint8Array for witness program\n */\nexport function hash160ToBytes(hash160Hex: string): Uint8Array {\n const matches = hash160Hex.match(/../g);\n if (!matches) return new Uint8Array(0);\n return Uint8Array.from(matches.map((x) => parseInt(x, 16)));\n}\n\n/**\n * Generate bech32 address from public key\n * @param publicKey - Compressed public key as hex string\n * @param prefix - Address prefix (default: \"alpha\")\n * @param witnessVersion - Witness version (default: 0 for P2WPKH)\n * @returns Bech32 encoded address\n */\nexport function publicKeyToAddress(\n publicKey: string,\n prefix: string = 'alpha',\n witnessVersion: number = 0\n): string {\n const pubKeyHash = hash160(publicKey);\n const programBytes = hash160ToBytes(pubKeyHash);\n return encodeBech32(prefix, witnessVersion, programBytes);\n}\n\n/**\n * Get address info from private key\n */\nexport function privateKeyToAddressInfo(\n privateKey: string,\n prefix: string = 'alpha'\n): { address: string; publicKey: string } {\n const publicKey = getPublicKey(privateKey);\n const address = publicKeyToAddress(publicKey, prefix);\n return { address, publicKey };\n}\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Convert hex string to Uint8Array\n */\nexport function hexToBytes(hex: string): Uint8Array {\n const matches = hex.match(/../g);\n if (!matches) {\n return new Uint8Array(0);\n }\n return Uint8Array.from(matches.map((x) => parseInt(x, 16)));\n}\n\n/**\n * Convert Uint8Array to hex string\n */\nexport function bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Generate random bytes as hex string\n */\nexport function randomBytes(length: number): string {\n const words = CryptoJS.lib.WordArray.random(length);\n return words.toString(CryptoJS.enc.Hex);\n}\n\n// =============================================================================\n// High-Level Functions\n// =============================================================================\n\n/**\n * Generate identity from mnemonic\n * Returns master key derived from mnemonic seed\n */\nexport async function identityFromMnemonic(\n mnemonic: string,\n passphrase: string = ''\n): Promise<MasterKey> {\n if (!validateMnemonic(mnemonic)) {\n throw new SphereError('Invalid mnemonic phrase', 'INVALID_IDENTITY');\n }\n const seedHex = await mnemonicToSeed(mnemonic, passphrase);\n return generateMasterKey(seedHex);\n}\n\n/**\n * Synchronous version of identityFromMnemonic\n */\nexport function identityFromMnemonicSync(\n mnemonic: string,\n passphrase: string = ''\n): MasterKey {\n if (!validateMnemonic(mnemonic)) {\n throw new SphereError('Invalid mnemonic phrase', 'INVALID_IDENTITY');\n }\n const seedHex = mnemonicToSeedSync(mnemonic, passphrase);\n return generateMasterKey(seedHex);\n}\n\n/**\n * Derive address info at a specific path\n * @param masterKey - Master key with privateKey and chainCode\n * @param basePath - Base derivation path (e.g., \"m/44'/0'/0'\")\n * @param index - Address index\n * @param isChange - Whether this is a change address (chain 1 vs 0)\n * @param prefix - Address prefix (default: \"alpha\")\n */\nexport function deriveAddressInfo(\n masterKey: MasterKey,\n basePath: string,\n index: number,\n isChange: boolean = false,\n prefix: string = 'alpha'\n): AddressInfo {\n const chain = isChange ? 1 : 0;\n const fullPath = `${basePath}/${chain}/${index}`;\n\n const derived = deriveKeyAtPath(masterKey.privateKey, masterKey.chainCode, fullPath);\n const publicKey = getPublicKey(derived.privateKey);\n const address = publicKeyToAddress(publicKey, prefix);\n\n return {\n privateKey: derived.privateKey,\n publicKey,\n address,\n path: fullPath,\n index,\n };\n}\n\n/**\n * Generate full address info from private key with index and path\n * (L1 SDK compatibility)\n */\nexport function generateAddressInfo(\n privateKey: string,\n index: number,\n path: string,\n prefix: string = 'alpha'\n): AddressInfo {\n const { address, publicKey } = privateKeyToAddressInfo(privateKey, prefix);\n return {\n privateKey,\n publicKey,\n address,\n path,\n index,\n };\n}\n\n// =============================================================================\n// Message Signing (secp256k1 ECDSA with recoverable signature)\n// =============================================================================\n\n/** Prefix prepended to all signed messages (Bitcoin-like signed message format) */\nexport const SIGN_MESSAGE_PREFIX = 'Sphere Signed Message:\\n';\n\n/** Encode an integer as a Bitcoin-style compact varint */\nfunction varint(n: number): Uint8Array {\n if (n < 253) return new Uint8Array([n]);\n const buf = new Uint8Array(3);\n buf[0] = 253;\n buf[1] = n & 0xff;\n buf[2] = (n >> 8) & 0xff;\n return buf;\n}\n\n/**\n * Hash a message for signing using the Bitcoin-like double-SHA256 scheme:\n * SHA256(SHA256(varint(prefix.length) + prefix + varint(msg.length) + msg))\n *\n * @returns 64-char lowercase hex hash\n */\nexport function hashSignMessage(message: string): string {\n const prefix = new TextEncoder().encode(SIGN_MESSAGE_PREFIX);\n const msg = new TextEncoder().encode(message);\n const prefixLen = varint(prefix.length);\n const msgLen = varint(msg.length);\n const full = new Uint8Array(prefixLen.length + prefix.length + msgLen.length + msg.length);\n let off = 0;\n full.set(prefixLen, off); off += prefixLen.length;\n full.set(prefix, off); off += prefix.length;\n full.set(msgLen, off); off += msgLen.length;\n full.set(msg, off);\n const hex = Array.from(full).map(b => b.toString(16).padStart(2, '0')).join('');\n const h1 = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(hex)).toString();\n return CryptoJS.SHA256(CryptoJS.enc.Hex.parse(h1)).toString();\n}\n\n/**\n * Sign a message with a secp256k1 private key.\n *\n * Returns a 130-character hex string: v (2 chars) + r (64 chars) + s (64 chars).\n * The recovery byte `v` is `31 + recoveryParam` (0-3).\n *\n * @param privateKeyHex - 64-char hex private key\n * @param message - plaintext message to sign\n */\nexport function signMessage(privateKeyHex: string, message: string): string {\n const keyPair = ec.keyFromPrivate(privateKeyHex, 'hex');\n const hashHex = hashSignMessage(message);\n const hashBytes = Buffer.from(hashHex, 'hex');\n const sig = keyPair.sign(hashBytes, { canonical: true });\n\n // Find recovery parameter\n const pub = keyPair.getPublic();\n let recoveryParam = -1;\n for (let i = 0; i < 4; i++) {\n try {\n if (ec.recoverPubKey(hashBytes, sig, i).eq(pub)) {\n recoveryParam = i;\n break;\n }\n } catch { /* try next */ }\n }\n if (recoveryParam === -1) {\n throw new SphereError('Could not find recovery parameter', 'SIGNING_ERROR');\n }\n\n const v = (31 + recoveryParam).toString(16).padStart(2, '0');\n const r = sig.r.toString('hex').padStart(64, '0');\n const s = sig.s.toString('hex').padStart(64, '0');\n return v + r + s;\n}\n\n/**\n * Verify a signed message against a compressed secp256k1 public key.\n *\n * @param message - The original plaintext message\n * @param signature - 130-char hex signature (v + r + s)\n * @param expectedPubkey - 66-char compressed public key hex\n * @returns `true` if the signature is valid and matches the expected public key\n */\nexport function verifySignedMessage(\n message: string,\n signature: string,\n expectedPubkey: string,\n): boolean {\n if (signature.length !== 130) return false;\n\n const v = parseInt(signature.slice(0, 2), 16) - 31;\n const r = signature.slice(2, 66);\n const s = signature.slice(66, 130);\n\n if (v < 0 || v > 3) return false;\n\n const hashHex = hashSignMessage(message);\n const hashBytes = Buffer.from(hashHex, 'hex');\n\n try {\n const recovered = ec.recoverPubKey(hashBytes, { r, s }, v);\n const recoveredHex = recovered.encode('hex', true); // compressed\n return recoveredHex === expectedPubkey;\n } catch {\n return false;\n }\n}\n\n// Re-export elliptic instance for advanced use cases\nexport { ec };\n","/**\n * SDK Error Types\n *\n * Structured error codes for programmatic error handling in UI.\n * UI can switch on error.code to show appropriate user-facing messages.\n *\n * @example\n * ```ts\n * import { SphereError } from '@unicitylabs/sphere-sdk';\n *\n * try {\n * await sphere.payments.send({ ... });\n * } catch (err) {\n * if (err instanceof SphereError) {\n * switch (err.code) {\n * case 'INSUFFICIENT_BALANCE': showToast('Not enough funds'); break;\n * case 'INVALID_RECIPIENT': showToast('Recipient not found'); break;\n * case 'TRANSPORT_ERROR': showToast('Network connection issue'); break;\n * case 'TIMEOUT': showToast('Request timed out, try again'); break;\n * default: showToast(err.message);\n * }\n * }\n * }\n * ```\n */\n\nexport type SphereErrorCode =\n | 'NOT_INITIALIZED'\n | 'ALREADY_INITIALIZED'\n | 'INVALID_CONFIG'\n | 'INVALID_IDENTITY'\n | 'INSUFFICIENT_BALANCE'\n | 'INVALID_RECIPIENT'\n | 'TRANSFER_FAILED'\n | 'STORAGE_ERROR'\n | 'TRANSPORT_ERROR'\n | 'AGGREGATOR_ERROR'\n | 'VALIDATION_ERROR'\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'DECRYPTION_ERROR'\n | 'MODULE_NOT_AVAILABLE'\n | 'SIGNING_ERROR';\n\nexport class SphereError extends Error {\n readonly code: SphereErrorCode;\n readonly cause?: unknown;\n\n constructor(message: string, code: SphereErrorCode, cause?: unknown) {\n super(message);\n this.name = 'SphereError';\n this.code = code;\n this.cause = cause;\n }\n}\n\n/**\n * Type guard to check if an error is a SphereError\n */\nexport function isSphereError(err: unknown): err is SphereError {\n return err instanceof SphereError;\n}\n","/**\n * IPNS Key Derivation\n * Deterministic IPNS identity from secp256k1 private key using HKDF\n *\n * Derivation path:\n * secp256k1 privateKey (hex)\n * -> HKDF(sha256, key, info=\"ipfs-storage-ed25519-v1\", 32 bytes)\n * -> Ed25519 key pair\n * -> libp2p PeerId\n * -> IPNS name (e.g., \"12D3KooW...\")\n */\n\nimport { hkdf } from '@noble/hashes/hkdf.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { hexToBytes } from '../../../core/crypto';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/**\n * HKDF info string for deriving Ed25519 keys from wallet keys.\n * Must match sphere webapp for cross-device compatibility.\n */\nexport const IPNS_HKDF_INFO = 'ipfs-storage-ed25519-v1';\n\n// =============================================================================\n// Dynamic Import Cache\n// =============================================================================\n\nlet libp2pCryptoModule: typeof import('@libp2p/crypto/keys') | null = null;\nlet libp2pPeerIdModule: typeof import('@libp2p/peer-id') | null = null;\n\nasync function loadLibp2pModules() {\n if (!libp2pCryptoModule) {\n [libp2pCryptoModule, libp2pPeerIdModule] = await Promise.all([\n import('@libp2p/crypto/keys'),\n import('@libp2p/peer-id'),\n ]);\n }\n return {\n generateKeyPairFromSeed: libp2pCryptoModule!.generateKeyPairFromSeed,\n peerIdFromPrivateKey: libp2pPeerIdModule!.peerIdFromPrivateKey,\n };\n}\n\n// =============================================================================\n// Key Derivation\n// =============================================================================\n\n/**\n * Derive Ed25519 key material from a secp256k1 private key using HKDF.\n *\n * @param privateKeyHex - secp256k1 private key in hex format\n * @param info - HKDF info string (default: IPNS_HKDF_INFO)\n * @returns 32-byte derived key material suitable for Ed25519 seed\n */\nexport function deriveEd25519KeyMaterial(\n privateKeyHex: string,\n info: string = IPNS_HKDF_INFO,\n): Uint8Array {\n const walletSecret = hexToBytes(privateKeyHex);\n const infoBytes = new TextEncoder().encode(info);\n return hkdf(sha256, walletSecret, undefined, infoBytes, 32);\n}\n\n/**\n * Derive full IPNS identity (key pair + IPNS name) from a secp256k1 private key.\n *\n * @param privateKeyHex - secp256k1 private key in hex format\n * @returns Object with keyPair and ipnsName\n */\nexport async function deriveIpnsIdentity(\n privateKeyHex: string,\n): Promise<{ keyPair: unknown; ipnsName: string }> {\n const { generateKeyPairFromSeed, peerIdFromPrivateKey } = await loadLibp2pModules();\n\n const derivedKey = deriveEd25519KeyMaterial(privateKeyHex);\n const keyPair = await generateKeyPairFromSeed('Ed25519', derivedKey);\n const peerId = peerIdFromPrivateKey(keyPair);\n\n return {\n keyPair,\n ipnsName: peerId.toString(),\n };\n}\n\n/**\n * Derive just the IPNS name from a secp256k1 private key.\n * Lighter than deriveIpnsIdentity when you don't need the key pair.\n *\n * @param privateKeyHex - secp256k1 private key in hex format\n * @returns IPNS name string (e.g., \"12D3KooW...\")\n */\nexport async function deriveIpnsName(\n privateKeyHex: string,\n): Promise<string> {\n const { ipnsName } = await deriveIpnsIdentity(privateKeyHex);\n return ipnsName;\n}\n","/**\n * IPNS Record Manager\n * Creates, marshals, and parses IPNS records for publishing and resolution\n */\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Default IPNS record lifetime: 99 years (effectively permanent) */\nconst DEFAULT_LIFETIME_MS = 99 * 365 * 24 * 60 * 60 * 1000;\n\n// =============================================================================\n// Dynamic Import Cache\n// =============================================================================\n\nlet ipnsModule: {\n createIPNSRecord: typeof import('ipns')['createIPNSRecord'];\n marshalIPNSRecord: typeof import('ipns')['marshalIPNSRecord'];\n unmarshalIPNSRecord: typeof import('ipns')['unmarshalIPNSRecord'];\n} | null = null;\n\nasync function loadIpnsModule() {\n if (!ipnsModule) {\n const mod = await import('ipns');\n ipnsModule = {\n createIPNSRecord: mod.createIPNSRecord,\n marshalIPNSRecord: mod.marshalIPNSRecord,\n unmarshalIPNSRecord: mod.unmarshalIPNSRecord,\n };\n }\n return ipnsModule;\n}\n\n// =============================================================================\n// Record Creation\n// =============================================================================\n\n/**\n * Create a signed IPNS record and marshal it to bytes.\n *\n * @param keyPair - Ed25519 private key (from deriveIpnsIdentity)\n * @param cid - CID to point the IPNS record at\n * @param sequenceNumber - Monotonically increasing sequence number\n * @param lifetimeMs - Record validity period (default: 99 years)\n * @returns Marshalled IPNS record bytes\n */\nexport async function createSignedRecord(\n keyPair: unknown,\n cid: string,\n sequenceNumber: bigint,\n lifetimeMs: number = DEFAULT_LIFETIME_MS,\n): Promise<Uint8Array> {\n const { createIPNSRecord, marshalIPNSRecord } = await loadIpnsModule();\n\n const record = await createIPNSRecord(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n keyPair as any,\n `/ipfs/${cid}`,\n sequenceNumber,\n lifetimeMs,\n );\n\n return marshalIPNSRecord(record);\n}\n\n// =============================================================================\n// Record Parsing\n// =============================================================================\n\n/**\n * Parse a routing API response (NDJSON) to extract CID and sequence number.\n * The routing API returns newline-delimited JSON with an \"Extra\" field\n * containing a base64-encoded marshalled IPNS record.\n *\n * @param responseText - Raw text from the routing API response\n * @returns Parsed result with cid, sequence, and recordData, or null\n */\nexport async function parseRoutingApiResponse(\n responseText: string,\n): Promise<{ cid: string; sequence: bigint; recordData: Uint8Array } | null> {\n const { unmarshalIPNSRecord } = await loadIpnsModule();\n\n const lines = responseText.trim().split('\\n');\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const obj = JSON.parse(line);\n\n if (obj.Extra) {\n const recordData = base64ToUint8Array(obj.Extra);\n const record = unmarshalIPNSRecord(recordData);\n\n // Extract CID from the value field\n const valueBytes = typeof record.value === 'string'\n ? new TextEncoder().encode(record.value)\n : record.value as Uint8Array;\n const valueStr = new TextDecoder().decode(valueBytes);\n const cidMatch = valueStr.match(/\\/ipfs\\/([a-zA-Z0-9]+)/);\n\n if (cidMatch) {\n return {\n cid: cidMatch[1],\n sequence: record.sequence,\n recordData,\n };\n }\n }\n } catch {\n // Skip malformed lines\n continue;\n }\n }\n\n return null;\n}\n\n/**\n * Verify that a new sequence number represents a valid progression.\n *\n * @param newSeq - Proposed new sequence number\n * @param lastKnownSeq - Last known sequence number\n * @returns true if the new sequence is valid (greater than last known)\n */\nexport function verifySequenceProgression(\n newSeq: bigint,\n lastKnownSeq: bigint,\n): boolean {\n return newSeq > lastKnownSeq;\n}\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n","/**\n * IPFS Multi-Tier Cache\n * Caches IPNS records, content by CID, and tracks gateway failures\n */\n\nimport type { IpnsGatewayResult } from './ipfs-types';\nimport type { TxfStorageDataBase } from '../../../storage';\n\n// =============================================================================\n// Cache Entry Type\n// =============================================================================\n\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\n// =============================================================================\n// Default Config\n// =============================================================================\n\nconst DEFAULT_IPNS_TTL_MS = 60_000; // 60s for IPNS records\nconst DEFAULT_FAILURE_COOLDOWN_MS = 60_000; // 60s circuit breaker cooldown\nconst DEFAULT_FAILURE_THRESHOLD = 3; // 3 consecutive failures\nconst DEFAULT_KNOWN_FRESH_WINDOW_MS = 30_000; // 30s after publish\n\n// =============================================================================\n// Cache Implementation\n// =============================================================================\n\nexport interface IpfsCacheConfig {\n ipnsTtlMs?: number;\n failureCooldownMs?: number;\n failureThreshold?: number;\n knownFreshWindowMs?: number;\n}\n\nexport class IpfsCache {\n private readonly ipnsRecords = new Map<string, CacheEntry<IpnsGatewayResult>>();\n private readonly content = new Map<string, CacheEntry<TxfStorageDataBase>>();\n private readonly gatewayFailures = new Map<string, { count: number; lastFailure: number }>();\n private readonly knownFreshTimestamps = new Map<string, number>();\n\n private readonly ipnsTtlMs: number;\n private readonly failureCooldownMs: number;\n private readonly failureThreshold: number;\n private readonly knownFreshWindowMs: number;\n\n constructor(config?: IpfsCacheConfig) {\n this.ipnsTtlMs = config?.ipnsTtlMs ?? DEFAULT_IPNS_TTL_MS;\n this.failureCooldownMs = config?.failureCooldownMs ?? DEFAULT_FAILURE_COOLDOWN_MS;\n this.failureThreshold = config?.failureThreshold ?? DEFAULT_FAILURE_THRESHOLD;\n this.knownFreshWindowMs = config?.knownFreshWindowMs ?? DEFAULT_KNOWN_FRESH_WINDOW_MS;\n }\n\n // ---------------------------------------------------------------------------\n // IPNS Record Cache (60s TTL)\n // ---------------------------------------------------------------------------\n\n getIpnsRecord(ipnsName: string): IpnsGatewayResult | null {\n const entry = this.ipnsRecords.get(ipnsName);\n if (!entry) return null;\n\n if (Date.now() - entry.timestamp > this.ipnsTtlMs) {\n this.ipnsRecords.delete(ipnsName);\n return null;\n }\n\n return entry.data;\n }\n\n /**\n * Get cached IPNS record ignoring TTL (for known-fresh optimization).\n */\n getIpnsRecordIgnoreTtl(ipnsName: string): IpnsGatewayResult | null {\n const entry = this.ipnsRecords.get(ipnsName);\n return entry?.data ?? null;\n }\n\n setIpnsRecord(ipnsName: string, result: IpnsGatewayResult): void {\n this.ipnsRecords.set(ipnsName, {\n data: result,\n timestamp: Date.now(),\n });\n }\n\n invalidateIpns(ipnsName: string): void {\n this.ipnsRecords.delete(ipnsName);\n }\n\n // ---------------------------------------------------------------------------\n // Content Cache (infinite TTL - content is immutable by CID)\n // ---------------------------------------------------------------------------\n\n getContent(cid: string): TxfStorageDataBase | null {\n const entry = this.content.get(cid);\n return entry?.data ?? null;\n }\n\n setContent(cid: string, data: TxfStorageDataBase): void {\n this.content.set(cid, {\n data,\n timestamp: Date.now(),\n });\n }\n\n // ---------------------------------------------------------------------------\n // Gateway Failure Tracking (Circuit Breaker)\n // ---------------------------------------------------------------------------\n\n /**\n * Record a gateway failure. After threshold consecutive failures,\n * the gateway enters cooldown and is considered unhealthy.\n */\n recordGatewayFailure(gateway: string): void {\n const existing = this.gatewayFailures.get(gateway);\n this.gatewayFailures.set(gateway, {\n count: (existing?.count ?? 0) + 1,\n lastFailure: Date.now(),\n });\n }\n\n /** Reset failure count for a gateway (on successful request) */\n recordGatewaySuccess(gateway: string): void {\n this.gatewayFailures.delete(gateway);\n }\n\n /**\n * Check if a gateway is currently in circuit breaker cooldown.\n * A gateway is considered unhealthy if it has had >= threshold\n * consecutive failures and the cooldown period hasn't elapsed.\n */\n isGatewayInCooldown(gateway: string): boolean {\n const failure = this.gatewayFailures.get(gateway);\n if (!failure) return false;\n\n if (failure.count < this.failureThreshold) return false;\n\n const elapsed = Date.now() - failure.lastFailure;\n if (elapsed >= this.failureCooldownMs) {\n // Cooldown expired, reset\n this.gatewayFailures.delete(gateway);\n return false;\n }\n\n return true;\n }\n\n // ---------------------------------------------------------------------------\n // Known-Fresh Flag (FAST mode optimization)\n // ---------------------------------------------------------------------------\n\n /**\n * Mark IPNS cache as \"known-fresh\" (after local publish or push notification).\n * Within the fresh window, we can skip network resolution.\n */\n markIpnsFresh(ipnsName: string): void {\n this.knownFreshTimestamps.set(ipnsName, Date.now());\n }\n\n /**\n * Check if the cache is known-fresh (within the fresh window).\n */\n isIpnsKnownFresh(ipnsName: string): boolean {\n const timestamp = this.knownFreshTimestamps.get(ipnsName);\n if (!timestamp) return false;\n\n if (Date.now() - timestamp > this.knownFreshWindowMs) {\n this.knownFreshTimestamps.delete(ipnsName);\n return false;\n }\n\n return true;\n }\n\n // ---------------------------------------------------------------------------\n // Cache Management\n // ---------------------------------------------------------------------------\n\n clear(): void {\n this.ipnsRecords.clear();\n this.content.clear();\n this.gatewayFailures.clear();\n this.knownFreshTimestamps.clear();\n }\n}\n","/**\n * Centralized SDK Logger\n *\n * A lightweight singleton logger that works across all tsup bundles\n * by storing state on globalThis. Supports three log levels:\n * - debug: detailed messages (only shown when debug=true)\n * - warn: important warnings (ALWAYS shown regardless of debug flag)\n * - error: critical errors (ALWAYS shown regardless of debug flag)\n *\n * Global debug flag enables all logging. Per-tag overrides allow\n * granular control (e.g., only transport debug).\n *\n * @example\n * ```ts\n * import { logger } from '@unicitylabs/sphere-sdk';\n *\n * // Enable all debug logging\n * logger.configure({ debug: true });\n *\n * // Enable only specific tags\n * logger.setTagDebug('Nostr', true);\n *\n * // Usage in SDK classes\n * logger.debug('Payments', 'Transfer started', { amount, recipient });\n * logger.warn('Nostr', 'queryEvents timed out after 5s');\n * logger.error('Sphere', 'Critical failure', error);\n * ```\n */\n\nexport type LogLevel = 'debug' | 'warn' | 'error';\n\nexport type LogHandler = (level: LogLevel, tag: string, message: string, ...args: unknown[]) => void;\n\nexport interface LoggerConfig {\n /** Enable debug logging globally (default: false). When false, only warn and error messages are shown. */\n debug?: boolean;\n /** Custom log handler. If provided, replaces console output. Useful for tests or custom log sinks. */\n handler?: LogHandler | null;\n}\n\n// Use a unique symbol-like key on globalThis to share logger state across tsup bundles\nconst LOGGER_KEY = '__sphere_sdk_logger__';\n\ninterface LoggerState {\n debug: boolean;\n tags: Record<string, boolean>;\n handler: LogHandler | null;\n}\n\nfunction getState(): LoggerState {\n const g = globalThis as unknown as Record<string, unknown>;\n if (!g[LOGGER_KEY]) {\n g[LOGGER_KEY] = { debug: false, tags: {}, handler: null } satisfies LoggerState;\n }\n return g[LOGGER_KEY] as LoggerState;\n}\n\nfunction isEnabled(tag: string): boolean {\n const state = getState();\n // Per-tag override takes priority\n if (tag in state.tags) return state.tags[tag];\n // Fall back to global flag\n return state.debug;\n}\n\nexport const logger = {\n /**\n * Configure the logger. Can be called multiple times (last write wins).\n * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().\n */\n configure(config: LoggerConfig): void {\n const state = getState();\n if (config.debug !== undefined) state.debug = config.debug;\n if (config.handler !== undefined) state.handler = config.handler;\n },\n\n /**\n * Enable/disable debug logging for a specific tag.\n * Per-tag setting overrides the global debug flag.\n *\n * @example\n * ```ts\n * logger.setTagDebug('Nostr', true); // enable only Nostr logs\n * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true\n * ```\n */\n setTagDebug(tag: string, enabled: boolean): void {\n getState().tags[tag] = enabled;\n },\n\n /**\n * Clear per-tag override, falling back to global debug flag.\n */\n clearTagDebug(tag: string): void {\n delete getState().tags[tag];\n },\n\n /** Returns true if debug mode is enabled for the given tag (or globally). */\n isDebugEnabled(tag?: string): boolean {\n if (tag) return isEnabled(tag);\n return getState().debug;\n },\n\n /**\n * Debug-level log. Only shown when debug is enabled (globally or for this tag).\n * Use for detailed operational information.\n */\n debug(tag: string, message: string, ...args: unknown[]): void {\n if (!isEnabled(tag)) return;\n const state = getState();\n if (state.handler) {\n state.handler('debug', tag, message, ...args);\n } else {\n console.log(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Warning-level log. ALWAYS shown regardless of debug flag.\n * Use for important but non-critical issues (timeouts, retries, degraded state).\n */\n warn(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('warn', tag, message, ...args);\n } else {\n console.warn(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Error-level log. ALWAYS shown regardless of debug flag.\n * Use for critical failures that should never be silenced.\n */\n error(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('error', tag, message, ...args);\n } else {\n console.error(`[${tag}]`, message, ...args);\n }\n },\n\n /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */\n reset(): void {\n const g = globalThis as unknown as Record<string, unknown>;\n delete g[LOGGER_KEY];\n },\n};\n","/**\n * IPFS HTTP Client\n * Fetch-based HTTP API client for IPFS gateway operations.\n * Works in both browser and Node.js (native fetch available since Node 18+).\n */\n\nimport { logger } from '../../../core/logger';\nimport type {\n IpnsGatewayResult,\n IpnsProgressiveResult,\n IpnsPublishResult,\n GatewayHealthResult,\n} from './ipfs-types';\nimport type { TxfStorageDataBase } from '../../../storage';\nimport {\n IpfsError,\n classifyFetchError,\n classifyHttpStatus,\n} from './ipfs-error-types';\nimport { IpfsCache } from './ipfs-cache';\nimport { parseRoutingApiResponse } from './ipns-record-manager';\n\n// =============================================================================\n// Default Timeouts\n// =============================================================================\n\nconst DEFAULT_CONNECTIVITY_TIMEOUT_MS = 5000;\nconst DEFAULT_FETCH_TIMEOUT_MS = 15000;\nconst DEFAULT_RESOLVE_TIMEOUT_MS = 10000;\nconst DEFAULT_PUBLISH_TIMEOUT_MS = 30000;\nconst DEFAULT_GATEWAY_PATH_TIMEOUT_MS = 3000;\nconst DEFAULT_ROUTING_API_TIMEOUT_MS = 2000;\n\n// =============================================================================\n// HTTP Client\n// =============================================================================\n\nexport interface IpfsHttpClientConfig {\n gateways: string[];\n fetchTimeoutMs?: number;\n resolveTimeoutMs?: number;\n publishTimeoutMs?: number;\n connectivityTimeoutMs?: number;\n debug?: boolean;\n}\n\nexport class IpfsHttpClient {\n private readonly gateways: string[];\n private readonly fetchTimeoutMs: number;\n private readonly resolveTimeoutMs: number;\n private readonly publishTimeoutMs: number;\n private readonly connectivityTimeoutMs: number;\n private readonly debug: boolean;\n private readonly cache: IpfsCache;\n\n constructor(config: IpfsHttpClientConfig, cache: IpfsCache) {\n this.gateways = config.gateways;\n this.fetchTimeoutMs = config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;\n this.resolveTimeoutMs = config.resolveTimeoutMs ?? DEFAULT_RESOLVE_TIMEOUT_MS;\n this.publishTimeoutMs = config.publishTimeoutMs ?? DEFAULT_PUBLISH_TIMEOUT_MS;\n this.connectivityTimeoutMs = config.connectivityTimeoutMs ?? DEFAULT_CONNECTIVITY_TIMEOUT_MS;\n this.debug = config.debug ?? false;\n this.cache = cache;\n }\n\n // ---------------------------------------------------------------------------\n // Public Accessors\n // ---------------------------------------------------------------------------\n\n /**\n * Get configured gateway URLs.\n */\n getGateways(): string[] {\n return [...this.gateways];\n }\n\n // ---------------------------------------------------------------------------\n // Gateway Health\n // ---------------------------------------------------------------------------\n\n /**\n * Test connectivity to a single gateway.\n */\n async testConnectivity(gateway: string): Promise<GatewayHealthResult> {\n const start = Date.now();\n try {\n const response = await this.fetchWithTimeout(\n `${gateway}/api/v0/version`,\n this.connectivityTimeoutMs,\n { method: 'POST' },\n );\n\n if (!response.ok) {\n return { gateway, healthy: false, error: `HTTP ${response.status}` };\n }\n\n return {\n gateway,\n healthy: true,\n responseTimeMs: Date.now() - start,\n };\n } catch (error) {\n return {\n gateway,\n healthy: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n /**\n * Find healthy gateways from the configured list.\n */\n async findHealthyGateways(): Promise<string[]> {\n const results = await Promise.allSettled(\n this.gateways.map((gw) => this.testConnectivity(gw)),\n );\n\n return results\n .filter((r): r is PromiseFulfilledResult<GatewayHealthResult> =>\n r.status === 'fulfilled' && r.value.healthy)\n .map((r) => r.value.gateway);\n }\n\n /**\n * Get gateways that are not in circuit breaker cooldown.\n */\n getAvailableGateways(): string[] {\n return this.gateways.filter((gw) => !this.cache.isGatewayInCooldown(gw));\n }\n\n // ---------------------------------------------------------------------------\n // Content Upload\n // ---------------------------------------------------------------------------\n\n /**\n * Upload JSON content to IPFS.\n * Tries all gateways in parallel, returns first success.\n */\n async upload(data: unknown, gateways?: string[]): Promise<{ cid: string }> {\n const targets = gateways ?? this.getAvailableGateways();\n if (targets.length === 0) {\n throw new IpfsError('No gateways available for upload', 'NETWORK_ERROR');\n }\n\n const jsonBytes = new TextEncoder().encode(JSON.stringify(data));\n\n const promises = targets.map(async (gateway) => {\n try {\n const formData = new FormData();\n formData.append('file', new Blob([jsonBytes], { type: 'application/json' }), 'data.json');\n\n const response = await this.fetchWithTimeout(\n `${gateway}/api/v0/add?pin=true&cid-version=1`,\n this.publishTimeoutMs,\n { method: 'POST', body: formData },\n );\n\n if (!response.ok) {\n throw new IpfsError(\n `Upload failed: HTTP ${response.status}`,\n classifyHttpStatus(response.status),\n gateway,\n );\n }\n\n const result = await response.json();\n this.cache.recordGatewaySuccess(gateway);\n this.log(`Uploaded to ${gateway}: CID=${result.Hash}`);\n return { cid: result.Hash as string, gateway };\n } catch (error) {\n if (error instanceof IpfsError && error.shouldTriggerCircuitBreaker) {\n this.cache.recordGatewayFailure(gateway);\n }\n throw error;\n }\n });\n\n try {\n const result = await Promise.any(promises);\n return { cid: result.cid };\n } catch (error) {\n if (error instanceof AggregateError) {\n throw new IpfsError(\n `Upload failed on all gateways: ${error.errors.map(e => e.message).join('; ')}`,\n 'NETWORK_ERROR',\n );\n }\n throw error;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Content Fetch\n // ---------------------------------------------------------------------------\n\n /**\n * Fetch content by CID from IPFS gateways.\n * Checks content cache first. Races all gateways for fastest response.\n */\n async fetchContent<T extends TxfStorageDataBase>(\n cid: string,\n gateways?: string[],\n ): Promise<T> {\n // Check content cache (infinite TTL for immutable content)\n const cached = this.cache.getContent(cid);\n if (cached) {\n this.log(`Content cache hit for CID=${cid}`);\n return cached as T;\n }\n\n const targets = gateways ?? this.getAvailableGateways();\n if (targets.length === 0) {\n throw new IpfsError('No gateways available for fetch', 'NETWORK_ERROR');\n }\n\n const promises = targets.map(async (gateway) => {\n try {\n const response = await this.fetchWithTimeout(\n `${gateway}/ipfs/${cid}`,\n this.fetchTimeoutMs,\n { headers: { Accept: 'application/octet-stream' } },\n );\n\n if (!response.ok) {\n const body = await response.text().catch((err) => { logger.debug('IPFS-HTTP', 'Failed to read error response body', err); return ''; });\n throw new IpfsError(\n `Fetch failed: HTTP ${response.status}`,\n classifyHttpStatus(response.status, body),\n gateway,\n );\n }\n\n const data = await response.json() as T;\n this.cache.recordGatewaySuccess(gateway);\n this.cache.setContent(cid, data);\n this.log(`Fetched from ${gateway}: CID=${cid}`);\n return data;\n } catch (error) {\n if (error instanceof IpfsError && error.shouldTriggerCircuitBreaker) {\n this.cache.recordGatewayFailure(gateway);\n }\n throw error;\n }\n });\n\n try {\n return await Promise.any(promises);\n } catch (error) {\n if (error instanceof AggregateError) {\n throw new IpfsError(\n `Fetch failed on all gateways for CID=${cid}`,\n 'NETWORK_ERROR',\n );\n }\n throw error;\n }\n }\n\n // ---------------------------------------------------------------------------\n // IPNS Resolution\n // ---------------------------------------------------------------------------\n\n /**\n * Resolve IPNS via Routing API (returns record with sequence number).\n * POST /api/v0/routing/get?arg=/ipns/{name}\n */\n async resolveIpnsViaRoutingApi(\n gateway: string,\n ipnsName: string,\n timeoutMs: number = DEFAULT_ROUTING_API_TIMEOUT_MS,\n ): Promise<IpnsGatewayResult | null> {\n try {\n const response = await this.fetchWithTimeout(\n `${gateway}/api/v0/routing/get?arg=/ipns/${ipnsName}`,\n timeoutMs,\n { method: 'POST' },\n );\n\n if (!response.ok) {\n const body = await response.text().catch((err) => { logger.debug('IPFS-HTTP', 'Failed to read error response body', err); return ''; });\n const category = classifyHttpStatus(response.status, body);\n if (category === 'NOT_FOUND') return null;\n throw new IpfsError(`Routing API: HTTP ${response.status}`, category, gateway);\n }\n\n const text = await response.text();\n const parsed = await parseRoutingApiResponse(text);\n\n if (!parsed) return null;\n\n this.cache.recordGatewaySuccess(gateway);\n return {\n cid: parsed.cid,\n sequence: parsed.sequence,\n gateway,\n recordData: parsed.recordData,\n };\n } catch (error) {\n if (error instanceof IpfsError) throw error;\n const category = classifyFetchError(error);\n if (category !== 'NOT_FOUND') {\n this.cache.recordGatewayFailure(gateway);\n }\n return null;\n }\n }\n\n /**\n * Resolve IPNS via gateway path (simpler, no sequence number).\n * GET /ipns/{name}?format=dag-json\n */\n async resolveIpnsViaGatewayPath(\n gateway: string,\n ipnsName: string,\n timeoutMs: number = DEFAULT_GATEWAY_PATH_TIMEOUT_MS,\n ): Promise<{ cid: string; content?: unknown } | null> {\n try {\n const response = await this.fetchWithTimeout(\n `${gateway}/ipns/${ipnsName}`,\n timeoutMs,\n { headers: { Accept: 'application/json' } },\n );\n\n if (!response.ok) return null;\n\n const content = await response.json();\n const cidHeader = response.headers.get('X-Ipfs-Path');\n if (cidHeader) {\n const match = cidHeader.match(/\\/ipfs\\/([a-zA-Z0-9]+)/);\n if (match) {\n this.cache.recordGatewaySuccess(gateway);\n return { cid: match[1], content };\n }\n }\n\n return { cid: '', content };\n } catch (err) {\n logger.debug('IPFS-HTTP', 'IPNS gateway resolution failed', err);\n return null;\n }\n }\n\n /**\n * Progressive IPNS resolution across all gateways.\n * Queries all gateways in parallel, returns highest sequence number.\n */\n async resolveIpns(\n ipnsName: string,\n gateways?: string[],\n ): Promise<IpnsProgressiveResult> {\n const targets = gateways ?? this.getAvailableGateways();\n if (targets.length === 0) {\n return { best: null, allResults: [], respondedCount: 0, totalGateways: 0 };\n }\n\n const results: IpnsGatewayResult[] = [];\n let respondedCount = 0;\n\n const promises = targets.map(async (gateway) => {\n const result = await this.resolveIpnsViaRoutingApi(\n gateway,\n ipnsName,\n this.resolveTimeoutMs,\n );\n if (result) results.push(result);\n respondedCount++;\n return result;\n });\n\n // Wait for all to complete (with overall timeout)\n await Promise.race([\n Promise.allSettled(promises),\n new Promise<void>((resolve) =>\n setTimeout(resolve, this.resolveTimeoutMs + 1000)),\n ]);\n\n // Find best result (highest sequence)\n let best: IpnsGatewayResult | null = null;\n for (const result of results) {\n if (!best || result.sequence > best.sequence) {\n best = result;\n }\n }\n\n if (best) {\n this.cache.setIpnsRecord(ipnsName, best);\n }\n\n return {\n best,\n allResults: results,\n respondedCount,\n totalGateways: targets.length,\n };\n }\n\n // ---------------------------------------------------------------------------\n // IPNS Publishing\n // ---------------------------------------------------------------------------\n\n /**\n * Publish IPNS record to a single gateway via routing API.\n */\n async publishIpnsViaRoutingApi(\n gateway: string,\n ipnsName: string,\n marshalledRecord: Uint8Array,\n timeoutMs: number = DEFAULT_PUBLISH_TIMEOUT_MS,\n ): Promise<boolean> {\n try {\n const formData = new FormData();\n formData.append(\n 'file',\n new Blob([new Uint8Array(marshalledRecord)]),\n 'record',\n );\n\n const response = await this.fetchWithTimeout(\n `${gateway}/api/v0/routing/put?arg=/ipns/${ipnsName}&allow-offline=true`,\n timeoutMs,\n { method: 'POST', body: formData },\n );\n\n if (!response.ok) {\n const errorText = await response.text().catch((err) => { logger.debug('IPFS-HTTP', 'Failed to read error response body', err); return ''; });\n throw new IpfsError(\n `IPNS publish: HTTP ${response.status}: ${errorText.slice(0, 100)}`,\n classifyHttpStatus(response.status, errorText),\n gateway,\n );\n }\n\n this.cache.recordGatewaySuccess(gateway);\n this.log(`IPNS published to ${gateway}: ${ipnsName}`);\n return true;\n } catch (error) {\n if (error instanceof IpfsError && error.shouldTriggerCircuitBreaker) {\n this.cache.recordGatewayFailure(gateway);\n }\n this.log(`IPNS publish to ${gateway} failed: ${error}`);\n return false;\n }\n }\n\n /**\n * Publish IPNS record to all gateways in parallel.\n */\n async publishIpns(\n ipnsName: string,\n marshalledRecord: Uint8Array,\n gateways?: string[],\n ): Promise<IpnsPublishResult> {\n const targets = gateways ?? this.getAvailableGateways();\n if (targets.length === 0) {\n return { success: false, error: 'No gateways available' };\n }\n\n const results = await Promise.allSettled(\n targets.map((gw) =>\n this.publishIpnsViaRoutingApi(gw, ipnsName, marshalledRecord, this.publishTimeoutMs)),\n );\n\n const successfulGateways: string[] = [];\n results.forEach((result, index) => {\n if (result.status === 'fulfilled' && result.value) {\n successfulGateways.push(targets[index]);\n }\n });\n\n return {\n success: successfulGateways.length > 0,\n ipnsName,\n successfulGateways,\n error: successfulGateways.length === 0 ? 'All gateways failed' : undefined,\n };\n }\n\n // ---------------------------------------------------------------------------\n // IPNS Verification\n // ---------------------------------------------------------------------------\n\n /**\n * Verify IPNS record persistence after publishing.\n * Retries resolution to confirm the record was accepted.\n */\n async verifyIpnsRecord(\n ipnsName: string,\n expectedSeq: bigint,\n expectedCid: string,\n retries: number = 3,\n delayMs: number = 1000,\n ): Promise<boolean> {\n for (let i = 0; i < retries; i++) {\n if (i > 0) {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n const { best } = await this.resolveIpns(ipnsName);\n if (best && best.sequence >= expectedSeq && best.cid === expectedCid) {\n return true;\n }\n }\n return false;\n }\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n private async fetchWithTimeout(\n url: string,\n timeoutMs: number,\n options?: RequestInit,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n return await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n private log(message: string): void {\n logger.debug('IPFS-HTTP', message);\n }\n}\n","/**\n * TXF Data Merge / Conflict Resolution\n * Merges local and remote TXF storage data with proper conflict handling\n */\n\nimport type { TxfStorageDataBase, TxfTombstone } from '../../../storage';\nimport type { MergeResult } from './ipfs-types';\n\n// =============================================================================\n// Merge Logic\n// =============================================================================\n\n/**\n * Merge local and remote TXF data.\n *\n * Rules:\n * 1. Meta: Higher version wins as base; increment by 1\n * 2. Tombstones: Union by composite key (tokenId, stateHash)\n * 3. Token entries: present in only one source -> add; both -> local wins\n * 4. Tombstone filtering: exclude tokens present in merged tombstones\n * 5. Outbox/Sent: Union with dedup by id/tokenId\n */\nexport function mergeTxfData<T extends TxfStorageDataBase>(\n local: T,\n remote: T,\n): MergeResult<T> {\n let added = 0;\n let removed = 0;\n let conflicts = 0;\n\n // 1. Merge meta — use higher version as base, increment\n const localVersion = local._meta?.version ?? 0;\n const remoteVersion = remote._meta?.version ?? 0;\n const baseMeta = localVersion >= remoteVersion ? local._meta : remote._meta;\n const mergedMeta = {\n ...baseMeta,\n version: Math.max(localVersion, remoteVersion) + 1,\n updatedAt: Date.now(),\n };\n\n // 2. Merge tombstones — union by composite key (tokenId + stateHash)\n const mergedTombstones = mergeTombstones(\n local._tombstones ?? [],\n remote._tombstones ?? [],\n );\n const tombstoneKeys = new Set(\n mergedTombstones.map((t) => `${t.tokenId}:${t.stateHash}`),\n );\n\n // 3. Merge token entries\n const localTokenKeys = getTokenKeys(local);\n const remoteTokenKeys = getTokenKeys(remote);\n const allTokenKeys = new Set([...localTokenKeys, ...remoteTokenKeys]);\n\n const mergedTokens: Record<string, unknown> = {};\n\n for (const key of allTokenKeys) {\n const tokenId = key.startsWith('_') ? key.slice(1) : key;\n const localToken = (local as Record<string, unknown>)[key];\n const remoteToken = (remote as Record<string, unknown>)[key];\n\n // Check tombstone filter\n if (isTokenTombstoned(tokenId, localToken, remoteToken, tombstoneKeys)) {\n if (localTokenKeys.has(key)) removed++;\n continue;\n }\n\n if (localToken && !remoteToken) {\n // Only in local\n mergedTokens[key] = localToken;\n } else if (!localToken && remoteToken) {\n // Only in remote\n mergedTokens[key] = remoteToken;\n added++;\n } else if (localToken && remoteToken) {\n // In both — local wins (with conflict count)\n mergedTokens[key] = localToken;\n conflicts++;\n }\n }\n\n // 4. Merge outbox — union with dedup by id\n const mergedOutbox = mergeArrayById(\n local._outbox ?? [],\n remote._outbox ?? [],\n 'id',\n );\n\n // 5. Merge sent — union with dedup by tokenId\n const mergedSent = mergeArrayById(\n local._sent ?? [],\n remote._sent ?? [],\n 'tokenId',\n );\n\n // 6. Merge invalid — union with dedup by tokenId\n const mergedInvalid = mergeArrayById(\n local._invalid ?? [],\n remote._invalid ?? [],\n 'tokenId',\n );\n\n // 7. Merge nametags — union by name, local wins on conflict\n type NametagEntry = { name: string; [key: string]: unknown };\n const localNametags = (local._nametags ?? []) as NametagEntry[];\n const remoteNametags = (remote._nametags ?? []) as NametagEntry[];\n const mergedNametags = mergeNametagsByName(localNametags, remoteNametags);\n\n // 8. Merge history — union with dedup by dedupKey (same pattern as _outbox/_sent/_invalid)\n const localHistory = (local._history ?? []) as Array<{ dedupKey: string }>;\n const remoteHistory = (remote._history ?? []) as Array<{ dedupKey: string }>;\n const mergedHistory = mergeArrayById(localHistory, remoteHistory, 'dedupKey');\n\n // Build merged result\n const merged = {\n _meta: mergedMeta,\n _tombstones: mergedTombstones.length > 0 ? mergedTombstones : undefined,\n _nametags: mergedNametags.length > 0 ? mergedNametags : undefined,\n _outbox: mergedOutbox.length > 0 ? mergedOutbox : undefined,\n _sent: mergedSent.length > 0 ? mergedSent : undefined,\n _invalid: mergedInvalid.length > 0 ? mergedInvalid : undefined,\n _history: mergedHistory.length > 0 ? mergedHistory : undefined,\n ...mergedTokens,\n } as unknown as T;\n\n return { merged, added, removed, conflicts };\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/**\n * Merge tombstone arrays by composite key (tokenId + stateHash).\n * On duplicates, keep the one with the newer timestamp.\n */\nfunction mergeTombstones(\n local: TxfTombstone[],\n remote: TxfTombstone[],\n): TxfTombstone[] {\n const merged = new Map<string, TxfTombstone>();\n\n for (const tombstone of [...local, ...remote]) {\n const key = `${tombstone.tokenId}:${tombstone.stateHash}`;\n const existing = merged.get(key);\n if (!existing || tombstone.timestamp > existing.timestamp) {\n merged.set(key, tombstone);\n }\n }\n\n return Array.from(merged.values());\n}\n\n/**\n * Get all token entry keys from TXF data.\n * Token keys start with '_' but are not meta fields.\n */\nfunction getTokenKeys(data: TxfStorageDataBase): Set<string> {\n const reservedKeys = new Set([\n '_meta', '_tombstones', '_outbox', '_sent', '_invalid',\n '_nametag', '_nametags', '_mintOutbox', '_invalidatedNametags', '_integrity',\n '_history',\n ]);\n const keys = new Set<string>();\n\n for (const key of Object.keys(data)) {\n if (reservedKeys.has(key)) continue;\n // Exclude archived and forked entries from token merge\n if (key.startsWith('archived-') || key.startsWith('_forked_')) continue;\n keys.add(key);\n }\n\n return keys;\n}\n\n/**\n * Check if a token should be filtered by tombstones.\n */\nfunction isTokenTombstoned(\n tokenId: string,\n localToken: unknown,\n remoteToken: unknown,\n tombstoneKeys: Set<string>,\n): boolean {\n // Check if any variant of this token is tombstoned\n // We check generic tokenId matching since we may not have the stateHash here\n for (const key of tombstoneKeys) {\n if (key.startsWith(`${tokenId}:`)) {\n return true;\n }\n }\n // Keep token if not tombstoned\n void localToken;\n void remoteToken;\n return false;\n}\n\n/**\n * Merge nametag arrays by name, deduplicating.\n * On duplicates, keep the local entry.\n */\nfunction mergeNametagsByName(\n local: Array<{ name: string; [key: string]: unknown }>,\n remote: Array<{ name: string; [key: string]: unknown }>,\n): Array<{ name: string; [key: string]: unknown }> {\n const seen = new Map<string, { name: string; [key: string]: unknown }>();\n\n for (const item of local) {\n if (item.name) seen.set(item.name, item);\n }\n for (const item of remote) {\n if (item.name && !seen.has(item.name)) {\n seen.set(item.name, item);\n }\n }\n\n return Array.from(seen.values());\n}\n\n/**\n * Merge arrays by a key field, deduplicating.\n * On duplicates, keep the one from the first array (local).\n */\nfunction mergeArrayById<T>(\n local: T[],\n remote: T[],\n idField: keyof T,\n): T[] {\n const seen = new Map<unknown, T>();\n\n for (const item of local) {\n const id = item[idField];\n if (id !== undefined) {\n seen.set(id, item);\n }\n }\n\n for (const item of remote) {\n const id = item[idField];\n if (id !== undefined && !seen.has(id)) {\n seen.set(id, item);\n }\n }\n\n return Array.from(seen.values());\n}\n","/**\n * WebSocket Abstraction\n * Platform-independent WebSocket interface for cross-platform support\n */\n\n// =============================================================================\n// WebSocket Interface\n// =============================================================================\n\n/**\n * Minimal WebSocket interface compatible with browser and Node.js\n */\nexport interface IWebSocket {\n readonly readyState: number;\n\n send(data: string): void;\n close(code?: number, reason?: string): void;\n\n onopen: ((event: unknown) => void) | null;\n onclose: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n onmessage: ((event: IMessageEvent) => void) | null;\n}\n\nexport interface IMessageEvent {\n data: string;\n}\n\n/**\n * WebSocket ready states (same as native WebSocket)\n */\nexport const WebSocketReadyState = {\n CONNECTING: 0,\n OPEN: 1,\n CLOSING: 2,\n CLOSED: 3,\n} as const;\n\n/**\n * Factory function to create WebSocket instances\n * Different implementations for browser (native) vs Node.js (ws package)\n */\nexport type WebSocketFactory = (url: string) => IWebSocket;\n\n// =============================================================================\n// UUID Generator\n// =============================================================================\n\n/**\n * Generate a unique ID (platform-independent)\n * Browser: crypto.randomUUID()\n * Node: crypto.randomUUID() or uuid package\n */\nexport type UUIDGenerator = () => string;\n\n/**\n * Default UUID generator using crypto.randomUUID\n * Works in modern browsers and Node 19+\n */\nexport function defaultUUIDGenerator(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older environments\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n","/**\n * WebSocket client for IPNS subscription updates.\n * Connects to IPFS sidecar and receives push notifications for IPNS changes.\n *\n * Architecture:\n * - Connects to /ws/ipns endpoint on IPFS gateway\n * - Clients subscribe to specific IPNS names\n * - Server pushes updates when IPNS records change\n * - Auto-reconnects on connection loss with exponential backoff\n * - Falls back to polling when WebSocket is unavailable\n *\n * Ported from Sphere webapp's IpnsSubscriptionClient.\n */\n\nimport { logger } from '../../../core/logger';\nimport type { IWebSocket, WebSocketFactory } from '../../../transport/websocket';\nimport { WebSocketReadyState } from '../../../transport/websocket';\nimport type { IpnsUpdateEvent } from './ipfs-types';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface IpnsSubscriptionClientConfig {\n /** WebSocket URL (e.g., wss://host/ws/ipns) */\n wsUrl: string;\n /** Platform-specific WebSocket factory */\n createWebSocket: WebSocketFactory;\n /** Ping interval in ms (default: 30000) */\n pingIntervalMs?: number;\n /** Initial reconnect delay in ms (default: 5000) */\n reconnectDelayMs?: number;\n /** Max reconnect delay in ms (default: 60000) */\n maxReconnectDelayMs?: number;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\ntype UpdateCallback = (update: IpnsUpdateEvent) => void;\n\ninterface WebSocketMessage {\n type: string;\n name?: string;\n names?: string[];\n sequence?: number;\n cid?: string | null;\n timestamp?: string;\n message?: string;\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\nexport class IpnsSubscriptionClient {\n private ws: IWebSocket | null = null;\n private readonly subscriptions: Map<string, Set<UpdateCallback>> = new Map();\n private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n private pingInterval: ReturnType<typeof setInterval> | null = null;\n private fallbackPollInterval: ReturnType<typeof setInterval> | null = null;\n\n private readonly wsUrl: string;\n private readonly createWebSocket: WebSocketFactory;\n private readonly pingIntervalMs: number;\n private readonly initialReconnectDelayMs: number;\n private readonly maxReconnectDelayMs: number;\n private readonly debugEnabled: boolean;\n\n private reconnectAttempts = 0;\n private isConnecting = false;\n private connectionOpenedAt = 0;\n private destroyed = false;\n\n /** Minimum stable connection time before resetting backoff (30 seconds) */\n private readonly minStableConnectionMs = 30000;\n\n private fallbackPollFn: (() => Promise<void>) | null = null;\n private fallbackPollIntervalMs = 0;\n\n constructor(config: IpnsSubscriptionClientConfig) {\n this.wsUrl = config.wsUrl;\n this.createWebSocket = config.createWebSocket;\n this.pingIntervalMs = config.pingIntervalMs ?? 30000;\n this.initialReconnectDelayMs = config.reconnectDelayMs ?? 5000;\n this.maxReconnectDelayMs = config.maxReconnectDelayMs ?? 60000;\n this.debugEnabled = config.debug ?? false;\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /**\n * Subscribe to IPNS updates for a specific name.\n * Automatically connects the WebSocket if not already connected.\n * If WebSocket is connecting, the name will be subscribed once connected.\n */\n subscribe(ipnsName: string, callback: UpdateCallback): () => void {\n if (!ipnsName || typeof ipnsName !== 'string') {\n this.log('Invalid IPNS name for subscription');\n return () => { /* noop */ };\n }\n\n const isNewSubscription = !this.subscriptions.has(ipnsName);\n\n if (isNewSubscription) {\n this.subscriptions.set(ipnsName, new Set());\n }\n\n this.subscriptions.get(ipnsName)!.add(callback);\n\n // Send subscription to server if already connected and this is a new name\n if (isNewSubscription && this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.sendSubscribe([ipnsName]);\n }\n\n // Connect if not already connected\n if (!this.ws || this.ws.readyState !== WebSocketReadyState.OPEN) {\n this.connect();\n }\n\n // Return unsubscribe function\n return () => {\n const callbacks = this.subscriptions.get(ipnsName);\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n this.subscriptions.delete(ipnsName);\n if (this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.sendUnsubscribe([ipnsName]);\n }\n // Disconnect if no more subscriptions\n if (this.subscriptions.size === 0) {\n this.disconnect();\n }\n }\n }\n };\n }\n\n /**\n * Register a convenience update callback for all subscriptions.\n * Returns an unsubscribe function.\n */\n onUpdate(callback: UpdateCallback): () => void {\n // Store as a special \"global\" callback keyed by '*'\n if (!this.subscriptions.has('*')) {\n this.subscriptions.set('*', new Set());\n }\n this.subscriptions.get('*')!.add(callback);\n\n return () => {\n const callbacks = this.subscriptions.get('*');\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n this.subscriptions.delete('*');\n }\n }\n };\n }\n\n /**\n * Set a fallback poll function to use when WebSocket is disconnected.\n * The poll function will be called at the specified interval while WS is down.\n */\n setFallbackPoll(fn: () => Promise<void>, intervalMs: number): void {\n this.fallbackPollFn = fn;\n this.fallbackPollIntervalMs = intervalMs;\n // Start polling if WS is not connected\n if (!this.isConnected()) {\n this.startFallbackPolling();\n }\n }\n\n /**\n * Connect to the WebSocket server.\n */\n connect(): void {\n if (this.destroyed) return;\n if (this.ws?.readyState === WebSocketReadyState.OPEN || this.isConnecting) {\n return;\n }\n\n this.isConnecting = true;\n\n try {\n this.log(`Connecting to ${this.wsUrl}...`);\n this.ws = this.createWebSocket(this.wsUrl);\n\n this.ws.onopen = () => {\n this.log('WebSocket connected');\n this.isConnecting = false;\n this.connectionOpenedAt = Date.now();\n\n // Resubscribe to all IPNS names (excluding '*' global key)\n const names = Array.from(this.subscriptions.keys()).filter(n => n !== '*');\n if (names.length > 0) {\n this.sendSubscribe(names);\n }\n\n // Start ping interval to keep connection alive\n this.startPingInterval();\n\n // Stop fallback polling — WS is live\n this.stopFallbackPolling();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n\n this.ws.onclose = () => {\n const connectionDuration = this.connectionOpenedAt > 0\n ? Date.now() - this.connectionOpenedAt\n : 0;\n const wasStable = connectionDuration >= this.minStableConnectionMs;\n\n this.log(`WebSocket closed (duration: ${Math.round(connectionDuration / 1000)}s)`);\n\n this.isConnecting = false;\n this.connectionOpenedAt = 0;\n this.stopPingInterval();\n\n // Only reset backoff if connection was stable\n if (wasStable) {\n this.reconnectAttempts = 0;\n }\n\n // Start fallback polling while WS is down\n this.startFallbackPolling();\n\n this.scheduleReconnect();\n };\n\n this.ws.onerror = () => {\n this.log('WebSocket error');\n this.isConnecting = false;\n };\n } catch (e) {\n this.log(`Failed to connect: ${e}`);\n this.isConnecting = false;\n this.startFallbackPolling();\n this.scheduleReconnect();\n }\n }\n\n /**\n * Disconnect from the WebSocket server and clean up.\n */\n disconnect(): void {\n this.destroyed = true;\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n this.stopPingInterval();\n this.stopFallbackPolling();\n\n if (this.ws) {\n this.ws.onopen = null;\n this.ws.onclose = null;\n this.ws.onerror = null;\n this.ws.onmessage = null;\n this.ws.close();\n this.ws = null;\n }\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n }\n\n /**\n * Check if connected to the WebSocket server.\n */\n isConnected(): boolean {\n return this.ws?.readyState === WebSocketReadyState.OPEN;\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Message Handling\n // ---------------------------------------------------------------------------\n\n private handleMessage(data: string): void {\n try {\n const message = JSON.parse(data) as WebSocketMessage;\n\n switch (message.type) {\n case 'update':\n if (message.name && message.sequence !== undefined) {\n this.notifySubscribers({\n type: 'update',\n name: message.name,\n sequence: message.sequence,\n cid: message.cid ?? '',\n timestamp: message.timestamp || new Date().toISOString(),\n });\n }\n break;\n\n case 'subscribed':\n this.log(`Subscribed to ${message.names?.length || 0} names`);\n break;\n\n case 'unsubscribed':\n this.log(`Unsubscribed from ${message.names?.length || 0} names`);\n break;\n\n case 'pong':\n // Keepalive response received\n break;\n\n case 'error':\n this.log(`Server error: ${message.message}`);\n break;\n\n default:\n // Unknown message type — ignore\n break;\n }\n } catch {\n this.log('Failed to parse message');\n }\n }\n\n private notifySubscribers(update: IpnsUpdateEvent & { type: string }): void {\n // Notify name-specific subscribers\n const callbacks = this.subscriptions.get(update.name);\n if (callbacks) {\n this.log(`Update: ${update.name.slice(0, 16)}... seq=${update.sequence}`);\n for (const callback of callbacks) {\n try {\n callback(update);\n } catch {\n // Don't let callback errors break the client\n }\n }\n }\n\n // Notify global subscribers\n const globalCallbacks = this.subscriptions.get('*');\n if (globalCallbacks) {\n for (const callback of globalCallbacks) {\n try {\n callback(update);\n } catch {\n // Don't let callback errors break the client\n }\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: WebSocket Send\n // ---------------------------------------------------------------------------\n\n private sendSubscribe(names: string[]): void {\n if (this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.ws.send(JSON.stringify({ action: 'subscribe', names }));\n }\n }\n\n private sendUnsubscribe(names: string[]): void {\n if (this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.ws.send(JSON.stringify({ action: 'unsubscribe', names }));\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Reconnection\n // ---------------------------------------------------------------------------\n\n /**\n * Schedule reconnection with exponential backoff.\n * Sequence: 5s, 10s, 20s, 40s, 60s (capped)\n */\n private scheduleReconnect(): void {\n if (this.destroyed || this.reconnectTimeout) return;\n\n // Don't reconnect if no subscriptions (excluding '*')\n const realSubscriptions = Array.from(this.subscriptions.keys()).filter(n => n !== '*');\n if (realSubscriptions.length === 0) return;\n\n this.reconnectAttempts++;\n const delay = Math.min(\n this.initialReconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1),\n this.maxReconnectDelayMs,\n );\n\n this.log(`Reconnecting in ${(delay / 1000).toFixed(1)}s (attempt ${this.reconnectAttempts})...`);\n\n this.reconnectTimeout = setTimeout(() => {\n this.reconnectTimeout = null;\n this.connect();\n }, delay);\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Keepalive\n // ---------------------------------------------------------------------------\n\n private startPingInterval(): void {\n this.stopPingInterval();\n this.pingInterval = setInterval(() => {\n if (this.ws?.readyState === WebSocketReadyState.OPEN) {\n this.ws.send(JSON.stringify({ action: 'ping' }));\n }\n }, this.pingIntervalMs);\n }\n\n private stopPingInterval(): void {\n if (this.pingInterval) {\n clearInterval(this.pingInterval);\n this.pingInterval = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Fallback Polling\n // ---------------------------------------------------------------------------\n\n private startFallbackPolling(): void {\n if (this.fallbackPollInterval || !this.fallbackPollFn || this.destroyed) return;\n\n this.log(`Starting fallback polling (${this.fallbackPollIntervalMs / 1000}s interval)`);\n\n // Run poll immediately once\n this.fallbackPollFn().catch((err) => { logger.warn('IPNS-WS', 'Fallback poll error:', err); });\n\n this.fallbackPollInterval = setInterval(() => {\n this.fallbackPollFn?.().catch((err) => { logger.warn('IPNS-WS', 'Fallback poll error:', err); });\n }, this.fallbackPollIntervalMs);\n }\n\n private stopFallbackPolling(): void {\n if (this.fallbackPollInterval) {\n clearInterval(this.fallbackPollInterval);\n this.fallbackPollInterval = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Logging\n // ---------------------------------------------------------------------------\n\n private log(message: string): void {\n logger.debug('IPNS-WS', message);\n }\n}\n","/**\n * Write-Behind Buffer for IpfsStorageProvider\n *\n * Provides non-blocking writes via double-buffering and a promise-chain\n * serial queue. Writes are accepted immediately into a pending buffer\n * and flushed to IPFS asynchronously in the background.\n */\n\nimport type { TxfStorageDataBase } from '../../../storage';\n\n// =============================================================================\n// AsyncSerialQueue\n// =============================================================================\n\n/**\n * Promise-chain-based async mutex. Serializes async operations\n * without external dependencies. Each enqueued operation waits for\n * the previous one to complete before starting.\n */\nexport class AsyncSerialQueue {\n private tail: Promise<void> = Promise.resolve();\n\n /** Enqueue an async operation. Returns when it completes. */\n enqueue<T>(fn: () => Promise<T>): Promise<T> {\n let resolve: (value: T) => void;\n let reject: (reason: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n this.tail = this.tail.then(\n () => fn().then(resolve!, reject!),\n () => fn().then(resolve!, reject!),\n );\n\n return promise;\n }\n}\n\n// =============================================================================\n// WriteBuffer\n// =============================================================================\n\n/**\n * Collects mutations (token saves/deletes + full TXF data)\n * between flush cycles. Acts as a staging area for changes\n * that haven't been persisted to IPFS yet.\n */\nexport class WriteBuffer {\n /** Full TXF data from save() calls — latest wins */\n txfData: TxfStorageDataBase | null = null;\n\n get isEmpty(): boolean {\n return this.txfData === null;\n }\n\n clear(): void {\n this.txfData = null;\n }\n\n /**\n * Merge another buffer's contents into this one (for rollback).\n * Existing (newer) mutations in `this` take precedence over `other`.\n */\n mergeFrom(other: WriteBuffer): void {\n if (other.txfData && !this.txfData) {\n this.txfData = other.txfData;\n }\n }\n}\n","/**\n * SDK2 Constants\n * Default configuration values and storage keys\n */\n\n// =============================================================================\n// Storage Keys\n// =============================================================================\n\n/** Default prefix for all storage keys */\nexport const STORAGE_PREFIX = 'sphere_' as const;\n\n/**\n * Default encryption key for wallet data\n * WARNING: This is a placeholder. In production, use user-provided password.\n * This key is used when no password is provided to encrypt/decrypt mnemonic.\n */\nexport const DEFAULT_ENCRYPTION_KEY = 'sphere-default-key' as const;\n\n/**\n * Global storage keys (one per wallet, no address index)\n * Final key format: sphere_{key}\n */\nexport const STORAGE_KEYS_GLOBAL = {\n /** Encrypted BIP39 mnemonic */\n MNEMONIC: 'mnemonic',\n /** Encrypted master private key */\n MASTER_KEY: 'master_key',\n /** BIP32 chain code */\n CHAIN_CODE: 'chain_code',\n /** HD derivation path (full path like m/44'/0'/0'/0/0) */\n DERIVATION_PATH: 'derivation_path',\n /** Base derivation path (like m/44'/0'/0' without chain/index) */\n BASE_PATH: 'base_path',\n /** Derivation mode: bip32, wif_hmac, legacy_hmac */\n DERIVATION_MODE: 'derivation_mode',\n /** Wallet source: mnemonic, file, unknown */\n WALLET_SOURCE: 'wallet_source',\n /** Wallet existence flag */\n WALLET_EXISTS: 'wallet_exists',\n /** Current active address index */\n CURRENT_ADDRESS_INDEX: 'current_address_index',\n /** Nametag cache per address (separate from tracked addresses registry) */\n ADDRESS_NAMETAGS: 'address_nametags',\n /** Active addresses registry (JSON: TrackedAddressesStorage) */\n TRACKED_ADDRESSES: 'tracked_addresses',\n /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */\n LAST_WALLET_EVENT_TS: 'last_wallet_event_ts',\n /** Group chat: last used relay URL (stale data detection) — global, same relay for all addresses */\n GROUP_CHAT_RELAY_URL: 'group_chat_relay_url',\n /** Cached token registry JSON (fetched from remote) */\n TOKEN_REGISTRY_CACHE: 'token_registry_cache',\n /** Timestamp of last token registry cache update (ms since epoch) */\n TOKEN_REGISTRY_CACHE_TS: 'token_registry_cache_ts',\n /** Cached price data JSON (from CoinGecko or other provider) */\n PRICE_CACHE: 'price_cache',\n /** Timestamp of last price cache update (ms since epoch) */\n PRICE_CACHE_TS: 'price_cache_ts',\n} as const;\n\n/**\n * Per-address storage keys (one per derived address)\n * Final key format: sphere_{DIRECT_xxx_yyy}_{key}\n * Example: sphere_DIRECT_abc123_xyz789_pending_transfers\n *\n * Note: Token data (tokens, tombstones, archived, forked) is stored via\n * TokenStorageProvider, not here. This avoids duplication.\n */\nexport const STORAGE_KEYS_ADDRESS = {\n /** Pending transfers for this address */\n PENDING_TRANSFERS: 'pending_transfers',\n /** Transfer outbox for this address */\n OUTBOX: 'outbox',\n /** Conversations for this address */\n CONVERSATIONS: 'conversations',\n /** Messages for this address */\n MESSAGES: 'messages',\n /** Transaction history for this address */\n TRANSACTION_HISTORY: 'transaction_history',\n /** Pending V5 finalization tokens (unconfirmed instant split tokens) */\n PENDING_V5_TOKENS: 'pending_v5_tokens',\n /** Group chat: joined groups for this address */\n GROUP_CHAT_GROUPS: 'group_chat_groups',\n /** Group chat: messages for this address */\n GROUP_CHAT_MESSAGES: 'group_chat_messages',\n /** Group chat: members for this address */\n GROUP_CHAT_MEMBERS: 'group_chat_members',\n /** Group chat: processed event IDs for deduplication */\n GROUP_CHAT_PROCESSED_EVENTS: 'group_chat_processed_events',\n /** Processed V5 split group IDs for Nostr re-delivery dedup */\n PROCESSED_SPLIT_GROUP_IDS: 'processed_split_group_ids',\n /** Processed V6 combined transfer IDs for Nostr re-delivery dedup */\n PROCESSED_COMBINED_TRANSFER_IDS: 'processed_combined_transfer_ids',\n} as const;\n\n/** @deprecated Use STORAGE_KEYS_GLOBAL and STORAGE_KEYS_ADDRESS instead */\nexport const STORAGE_KEYS = {\n ...STORAGE_KEYS_GLOBAL,\n ...STORAGE_KEYS_ADDRESS,\n} as const;\n\n/**\n * Build a per-address storage key using address identifier\n * @param addressId - Short identifier for the address (e.g., first 8 chars of pubkey hash, or direct address hash)\n * @param key - The key from STORAGE_KEYS_ADDRESS\n * @returns Key in format: \"{addressId}_{key}\" e.g., \"a1b2c3d4_tokens\"\n */\nexport function getAddressStorageKey(addressId: string, key: string): string {\n return `${addressId}_${key}`;\n}\n\n/**\n * Create a readable address identifier from directAddress or chainPubkey\n * Format: DIRECT_first6_last6 (sanitized for filesystem/storage)\n * @param directAddress - The L3 direct address (DIRECT:xxx) or chainPubkey\n * @returns Sanitized identifier like \"DIRECT_abc123_xyz789\"\n */\nexport function getAddressId(directAddress: string): string {\n // Remove DIRECT:// or DIRECT: prefix if present\n let hash = directAddress;\n if (hash.startsWith('DIRECT://')) {\n hash = hash.slice(9);\n } else if (hash.startsWith('DIRECT:')) {\n hash = hash.slice(7);\n }\n // Format: DIRECT_first6_last6 (sanitized)\n const first = hash.slice(0, 6).toLowerCase();\n const last = hash.slice(-6).toLowerCase();\n return `DIRECT_${first}_${last}`;\n}\n\n// =============================================================================\n// Nostr Defaults\n// =============================================================================\n\n/** Default Nostr relays */\nexport const DEFAULT_NOSTR_RELAYS = [\n 'wss://relay.unicity.network',\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.nostr.band',\n] as const;\n\n/** Nostr event kinds used by SDK - must match @unicitylabs/nostr-js-sdk */\nexport const NOSTR_EVENT_KINDS = {\n /** NIP-04 encrypted direct message */\n DIRECT_MESSAGE: 4,\n /** Token transfer (Unicity custom - 31113) */\n TOKEN_TRANSFER: 31113,\n /** Payment request (Unicity custom - 31115) */\n PAYMENT_REQUEST: 31115,\n /** Payment request response (Unicity custom - 31116) */\n PAYMENT_REQUEST_RESPONSE: 31116,\n /** Nametag binding (NIP-78 app-specific data) */\n NAMETAG_BINDING: 30078,\n /** Public broadcast */\n BROADCAST: 1,\n} as const;\n\n/**\n * NIP-29 Event Kinds for relay-based group chat\n * https://github.com/nostr-protocol/nips/blob/master/29.md\n */\nexport const NIP29_KINDS = {\n /** Chat message sent to group */\n CHAT_MESSAGE: 9,\n /** Thread root message */\n THREAD_ROOT: 11,\n /** Thread reply message */\n THREAD_REPLY: 12,\n /** User join request */\n JOIN_REQUEST: 9021,\n /** User leave request */\n LEAVE_REQUEST: 9022,\n /** Admin: add/update user */\n PUT_USER: 9000,\n /** Admin: remove user */\n REMOVE_USER: 9001,\n /** Admin: edit group metadata */\n EDIT_METADATA: 9002,\n /** Admin: delete event */\n DELETE_EVENT: 9005,\n /** Admin: create group */\n CREATE_GROUP: 9007,\n /** Admin: delete group */\n DELETE_GROUP: 9008,\n /** Admin: create invite code */\n CREATE_INVITE: 9009,\n /** Relay-signed group metadata */\n GROUP_METADATA: 39000,\n /** Relay-signed group admins */\n GROUP_ADMINS: 39001,\n /** Relay-signed group members */\n GROUP_MEMBERS: 39002,\n /** Relay-signed group roles */\n GROUP_ROLES: 39003,\n} as const;\n\n// =============================================================================\n// Aggregator (Oracle) Defaults\n// =============================================================================\n\n/**\n * Default aggregator URL\n * Note: The aggregator is conceptually an oracle - a trusted service that provides\n * verifiable truth about token state through cryptographic inclusion proofs.\n */\nexport const DEFAULT_AGGREGATOR_URL = 'https://aggregator.unicity.network/rpc' as const;\n\n/** Dev aggregator URL */\nexport const DEV_AGGREGATOR_URL = 'https://dev-aggregator.dyndns.org/rpc' as const;\n\n/** Test aggregator URL (Goggregator) */\nexport const TEST_AGGREGATOR_URL = 'https://goggregator-test.unicity.network' as const;\n\n/** Default aggregator request timeout (ms) */\nexport const DEFAULT_AGGREGATOR_TIMEOUT = 30000;\n\n/** Default API key for aggregator authentication */\nexport const DEFAULT_AGGREGATOR_API_KEY = 'sk_06365a9c44654841a366068bcfc68986' as const;\n\n// =============================================================================\n// IPFS Defaults\n// =============================================================================\n\n/** Default IPFS gateways */\nexport const DEFAULT_IPFS_GATEWAYS = [\n 'https://unicity-ipfs1.dyndns.org',\n] as const;\n\n/** Unicity IPFS bootstrap peers */\nexport const DEFAULT_IPFS_BOOTSTRAP_PEERS = [\n '/dns4/unicity-ipfs2.dyndns.org/tcp/4001/p2p/12D3KooWLNi5NDPPHbrfJakAQqwBqymYTTwMQXQKEWuCrJNDdmfh',\n '/dns4/unicity-ipfs3.dyndns.org/tcp/4001/p2p/12D3KooWQ4aujVE4ShLjdusNZBdffq3TbzrwT2DuWZY9H1Gxhwn6',\n '/dns4/unicity-ipfs4.dyndns.org/tcp/4001/p2p/12D3KooWJ1ByPfUzUrpYvgxKU8NZrR8i6PU1tUgMEbQX9Hh2DEn1',\n '/dns4/unicity-ipfs5.dyndns.org/tcp/4001/p2p/12D3KooWB1MdZZGHN5B8TvWXntbycfe7Cjcz7n6eZ9eykZadvmDv',\n] as const;\n\n/** Unicity dedicated IPFS nodes (HTTP API access) */\nexport const UNICITY_IPFS_NODES = [\n {\n host: 'unicity-ipfs1.dyndns.org',\n peerId: '12D3KooWDKJqEMAhH4nsSSiKtK1VLcas5coUqSPZAfbWbZpxtL4u',\n httpPort: 9080,\n httpsPort: 443,\n },\n] as const;\n\n/**\n * Get IPFS gateway URLs for HTTP API access.\n * @param isSecure - Use HTTPS (default: true). Set false for development.\n */\nexport function getIpfsGatewayUrls(isSecure?: boolean): string[] {\n return UNICITY_IPFS_NODES.map((node) =>\n isSecure !== false\n ? `https://${node.host}`\n : `http://${node.host}:${node.httpPort}`,\n );\n}\n\n// =============================================================================\n// Wallet Defaults\n// =============================================================================\n\n/** Default BIP32 base path (without chain/index) */\nexport const DEFAULT_BASE_PATH = \"m/44'/0'/0'\" as const;\n\n/** Default BIP32 derivation path (full path with chain/index) */\nexport const DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0` as const;\n\n/** Coin types */\nexport const COIN_TYPES = {\n /** ALPHA token (L1 blockchain) */\n ALPHA: 'ALPHA',\n /** Test token */\n TEST: 'TEST',\n} as const;\n\n// =============================================================================\n// L1 (ALPHA Blockchain) Defaults\n// =============================================================================\n\n/** Default Fulcrum electrum server for mainnet */\nexport const DEFAULT_ELECTRUM_URL = 'wss://fulcrum.unicity.network:50004' as const;\n\n/** Testnet Fulcrum electrum server */\nexport const TEST_ELECTRUM_URL = 'wss://fulcrum.unicity.network:50004' as const;\n\n// =============================================================================\n// Token Registry Defaults\n// =============================================================================\n\n/** Remote token registry URL (GitHub raw) */\nexport const TOKEN_REGISTRY_URL =\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json' as const;\n\n/** Default token registry refresh interval (ms) — 1 hour */\nexport const TOKEN_REGISTRY_REFRESH_INTERVAL = 3_600_000;\n\n// =============================================================================\n// Network Defaults\n// =============================================================================\n\n/** Testnet Nostr relays */\nexport const TEST_NOSTR_RELAYS = [\n 'wss://nostr-relay.testnet.unicity.network',\n] as const;\n\n/** Default group chat relays (NIP-29 Zooid relay) */\nexport const DEFAULT_GROUP_RELAYS = [\n 'wss://sphere-relay.unicity.network',\n] as const;\n\n/** Network configurations */\nexport const NETWORKS = {\n mainnet: {\n name: 'Mainnet',\n aggregatorUrl: DEFAULT_AGGREGATOR_URL,\n nostrRelays: DEFAULT_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: DEFAULT_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n testnet: {\n name: 'Testnet',\n aggregatorUrl: TEST_AGGREGATOR_URL,\n nostrRelays: TEST_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: TEST_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n dev: {\n name: 'Development',\n aggregatorUrl: DEV_AGGREGATOR_URL,\n nostrRelays: TEST_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: TEST_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n} as const;\n\nexport type NetworkType = keyof typeof NETWORKS;\nexport type NetworkConfig = (typeof NETWORKS)[NetworkType];\n\n// =============================================================================\n// Timeouts & Limits\n// =============================================================================\n\n/** Default timeouts (ms) */\nexport const TIMEOUTS = {\n /** WebSocket connection timeout */\n WEBSOCKET_CONNECT: 10000,\n /** Nostr relay reconnect delay */\n NOSTR_RECONNECT_DELAY: 3000,\n /** Max reconnect attempts */\n MAX_RECONNECT_ATTEMPTS: 5,\n /** Proof polling interval */\n PROOF_POLL_INTERVAL: 1000,\n /** Sync interval */\n SYNC_INTERVAL: 60000,\n} as const;\n\n// =============================================================================\n// Sphere Connect\n// =============================================================================\n\n/** Signal sent by wallet popup to dApp when ConnectHost is ready */\nexport const HOST_READY_TYPE = 'sphere-connect:host-ready' as const;\n\n/** Default timeout (ms) for waiting for the host-ready signal */\nexport const HOST_READY_TIMEOUT = 30_000;\n\n/** Validation limits */\nexport const LIMITS = {\n /** Min nametag length */\n NAMETAG_MIN_LENGTH: 3,\n /** Max nametag length */\n NAMETAG_MAX_LENGTH: 20,\n /** Max memo length */\n MEMO_MAX_LENGTH: 500,\n /** Max message length */\n MESSAGE_MAX_LENGTH: 10000,\n} as const;\n","/**\n * IPFS Storage Provider\n * Main TokenStorageProvider implementation using IPFS/IPNS.\n * Shared cross-platform module (browser + Node.js via native fetch).\n *\n * Uses a write-behind buffer for non-blocking save() operations.\n * Writes are accepted immediately and flushed to IPFS asynchronously.\n */\n\nimport { logger } from '../../../core/logger';\nimport { SphereError } from '../../../core/errors';\nimport type { ProviderStatus, FullIdentity } from '../../../types';\nimport type {\n TokenStorageProvider,\n TxfStorageDataBase,\n SaveResult,\n LoadResult,\n SyncResult,\n StorageEventCallback,\n StorageEvent,\n} from '../../../storage';\nimport type {\n IpfsStorageConfig,\n IpfsStatePersistence,\n} from './ipfs-types';\nimport type { WebSocketFactory } from '../../../transport/websocket';\nimport { getIpfsGatewayUrls } from '../../../constants';\nimport { IpfsCache } from './ipfs-cache';\nimport { IpfsHttpClient } from './ipfs-http-client';\nimport { IpnsSubscriptionClient } from './ipns-subscription-client';\nimport { deriveIpnsIdentity } from './ipns-key-derivation';\nimport { createSignedRecord } from './ipns-record-manager';\nimport { mergeTxfData } from './txf-merge';\nimport { InMemoryIpfsStatePersistence } from './ipfs-state-persistence';\nimport { AsyncSerialQueue, WriteBuffer } from './write-behind-buffer';\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\nexport class IpfsStorageProvider<TData extends TxfStorageDataBase = TxfStorageDataBase>\n implements TokenStorageProvider<TData>\n{\n readonly id = 'ipfs';\n readonly name = 'IPFS Storage';\n readonly type = 'p2p' as const;\n\n private status: ProviderStatus = 'disconnected';\n private identity: FullIdentity | null = null;\n private ipnsKeyPair: unknown = null;\n private ipnsName: string | null = null;\n private ipnsSequenceNumber: bigint = 0n;\n private lastCid: string | null = null;\n private lastKnownRemoteSequence: bigint = 0n;\n private dataVersion = 0;\n\n /**\n * The CID currently stored on the sidecar for this IPNS name.\n * Used as `_meta.lastCid` in the next save to satisfy chain validation.\n * - null for bootstrap (first-ever save)\n * - set after every successful save() or load()\n */\n private remoteCid: string | null = null;\n\n private readonly cache: IpfsCache;\n private readonly httpClient: IpfsHttpClient;\n private readonly statePersistence: IpfsStatePersistence;\n private readonly eventCallbacks: Set<StorageEventCallback> = new Set();\n private readonly debug: boolean;\n private readonly ipnsLifetimeMs: number;\n\n /** WebSocket factory for push subscriptions */\n private readonly createWebSocket: WebSocketFactory | undefined;\n /** Override WS URL */\n private readonly wsUrl: string | undefined;\n /** Fallback poll interval (default: 90000) */\n private readonly fallbackPollIntervalMs: number;\n /** IPNS subscription client for push notifications */\n private subscriptionClient: IpnsSubscriptionClient | null = null;\n /** Unsubscribe function from subscription client */\n private subscriptionUnsubscribe: (() => void) | null = null;\n\n /** Write-behind buffer: serializes flush / sync / shutdown */\n private readonly flushQueue = new AsyncSerialQueue();\n /** Pending mutations not yet flushed to IPFS */\n private pendingBuffer = new WriteBuffer();\n /** Debounce timer for background flush */\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n /** Debounce interval in ms */\n private readonly flushDebounceMs: number;\n /** Set to true during shutdown to prevent new flushes */\n private isShuttingDown = false;\n\n constructor(\n config?: IpfsStorageConfig,\n statePersistence?: IpfsStatePersistence,\n ) {\n const gateways = config?.gateways ?? getIpfsGatewayUrls();\n this.debug = config?.debug ?? false;\n this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? (99 * 365 * 24 * 60 * 60 * 1000);\n this.flushDebounceMs = config?.flushDebounceMs ?? 2000;\n\n this.cache = new IpfsCache({\n ipnsTtlMs: config?.ipnsCacheTtlMs,\n failureCooldownMs: config?.circuitBreakerCooldownMs,\n failureThreshold: config?.circuitBreakerThreshold,\n knownFreshWindowMs: config?.knownFreshWindowMs,\n });\n\n this.httpClient = new IpfsHttpClient({\n gateways,\n fetchTimeoutMs: config?.fetchTimeoutMs,\n resolveTimeoutMs: config?.resolveTimeoutMs,\n publishTimeoutMs: config?.publishTimeoutMs,\n connectivityTimeoutMs: config?.connectivityTimeoutMs,\n debug: this.debug,\n }, this.cache);\n\n this.statePersistence = statePersistence ?? new InMemoryIpfsStatePersistence();\n this.createWebSocket = config?.createWebSocket;\n this.wsUrl = config?.wsUrl;\n this.fallbackPollIntervalMs = config?.fallbackPollIntervalMs ?? 90000;\n }\n\n // ---------------------------------------------------------------------------\n // BaseProvider interface\n // ---------------------------------------------------------------------------\n\n async connect(): Promise<void> {\n await this.initialize();\n }\n\n async disconnect(): Promise<void> {\n await this.shutdown();\n }\n\n isConnected(): boolean {\n return this.status === 'connected';\n }\n\n getStatus(): ProviderStatus {\n return this.status;\n }\n\n // ---------------------------------------------------------------------------\n // Identity & Initialization\n // ---------------------------------------------------------------------------\n\n setIdentity(identity: FullIdentity): void {\n this.identity = identity;\n }\n\n async initialize(): Promise<boolean> {\n if (!this.identity) {\n this.log('Cannot initialize: no identity set');\n return false;\n }\n\n this.status = 'connecting';\n this.emitEvent({ type: 'storage:loading', timestamp: Date.now() });\n\n try {\n // Derive IPNS key pair and name from wallet private key\n const { keyPair, ipnsName } = await deriveIpnsIdentity(this.identity.privateKey);\n this.ipnsKeyPair = keyPair;\n this.ipnsName = ipnsName;\n this.log(`IPNS name derived: ${ipnsName}`);\n\n // Load persisted state\n const persisted = await this.statePersistence.load(ipnsName);\n if (persisted) {\n this.ipnsSequenceNumber = BigInt(persisted.sequenceNumber);\n this.lastCid = persisted.lastCid;\n this.remoteCid = persisted.lastCid; // chain link for next save\n this.dataVersion = persisted.version;\n this.log(`Loaded persisted state: seq=${this.ipnsSequenceNumber}, cid=${this.lastCid}`);\n }\n\n // Set up IPNS push subscription if WebSocket factory is available\n if (this.createWebSocket) {\n try {\n const wsUrlFinal = this.wsUrl ?? this.deriveWsUrl();\n if (wsUrlFinal) {\n this.subscriptionClient = new IpnsSubscriptionClient({\n wsUrl: wsUrlFinal,\n createWebSocket: this.createWebSocket,\n debug: this.debug,\n });\n\n // Subscribe to own IPNS name\n this.subscriptionUnsubscribe = this.subscriptionClient.subscribe(\n ipnsName,\n (update) => {\n this.log(`Push update: seq=${update.sequence}, cid=${update.cid}`);\n this.emitEvent({\n type: 'storage:remote-updated',\n timestamp: Date.now(),\n data: { name: update.name, sequence: update.sequence, cid: update.cid },\n });\n },\n );\n\n // Set fallback poll for when WS is disconnected\n this.subscriptionClient.setFallbackPoll(\n () => this.pollForRemoteChanges(),\n this.fallbackPollIntervalMs,\n );\n\n // Connect (non-blocking)\n this.subscriptionClient.connect();\n }\n } catch (wsError) {\n this.log(`Failed to set up IPNS subscription: ${wsError}`);\n // Non-fatal — provider works without push notifications\n }\n }\n\n // Test gateway connectivity (non-blocking, don't fail on it)\n this.httpClient.findHealthyGateways().then((healthy) => {\n if (healthy.length > 0) {\n this.log(`${healthy.length} healthy gateway(s) found`);\n } else {\n this.log('Warning: no healthy gateways found');\n }\n }).catch((err) => {\n logger.warn('IPFS-Storage', 'Gateway health check failed (non-fatal):', err);\n });\n\n this.isShuttingDown = false;\n this.status = 'connected';\n this.emitEvent({ type: 'storage:loaded', timestamp: Date.now() });\n return true;\n } catch (error) {\n this.status = 'error';\n this.emitEvent({\n type: 'storage:error',\n timestamp: Date.now(),\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n }\n\n async shutdown(): Promise<void> {\n this.isShuttingDown = true;\n\n // Cancel any pending debounced flush\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n\n // Final flush — drain any pending writes\n await this.flushQueue.enqueue(async () => {\n if (!this.pendingBuffer.isEmpty) {\n try {\n await this.executeFlush();\n } catch {\n this.log('Final flush on shutdown failed (data may be lost)');\n }\n }\n });\n\n // Disconnect subscription client\n if (this.subscriptionUnsubscribe) {\n this.subscriptionUnsubscribe();\n this.subscriptionUnsubscribe = null;\n }\n if (this.subscriptionClient) {\n this.subscriptionClient.disconnect();\n this.subscriptionClient = null;\n }\n\n this.cache.clear();\n this.status = 'disconnected';\n }\n\n // ---------------------------------------------------------------------------\n // Save (non-blocking — buffers data for async flush)\n // ---------------------------------------------------------------------------\n\n async save(data: TData): Promise<SaveResult> {\n if (!this.ipnsKeyPair || !this.ipnsName) {\n return { success: false, error: 'Not initialized', timestamp: Date.now() };\n }\n\n // Buffer the data for async flush\n this.pendingBuffer.txfData = data;\n this.scheduleFlush();\n\n // Return immediately — flush happens in background\n return { success: true, timestamp: Date.now() };\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Blocking save (used by sync and executeFlush)\n // ---------------------------------------------------------------------------\n\n /**\n * Perform the actual upload + IPNS publish synchronously.\n * Called by executeFlush() and sync() — never by public save().\n */\n private async _doSave(data: TData): Promise<SaveResult> {\n if (!this.ipnsKeyPair || !this.ipnsName) {\n return { success: false, error: 'Not initialized', timestamp: Date.now() };\n }\n\n this.emitEvent({ type: 'storage:saving', timestamp: Date.now() });\n\n try {\n // Update meta with chain-validation fields required by sidecar:\n // - lastCid: must equal the CID currently stored on sidecar (null for bootstrap)\n // - version: must be exactly current_version + 1 (for normal updates)\n this.dataVersion++;\n const metaUpdate: Record<string, unknown> = {\n ...data._meta,\n version: this.dataVersion,\n ipnsName: this.ipnsName,\n updatedAt: Date.now(),\n };\n if (this.remoteCid) {\n // Normal update: chain to previous CID\n metaUpdate.lastCid = this.remoteCid;\n }\n // Bootstrap (remoteCid is null): do NOT include lastCid field at all\n const updatedData = { ...data, _meta: metaUpdate } as unknown as Record<string, unknown>;\n\n // Upload to IPFS\n const { cid } = await this.httpClient.upload(updatedData);\n this.log(`Content uploaded: CID=${cid}`);\n\n // Compute new sequence: max(local, remote) + 1\n const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence\n ? this.ipnsSequenceNumber\n : this.lastKnownRemoteSequence;\n const newSeq = baseSeq + 1n;\n\n // Create signed IPNS record\n const marshalledRecord = await createSignedRecord(\n this.ipnsKeyPair,\n cid,\n newSeq,\n this.ipnsLifetimeMs,\n );\n\n // Publish to all gateways\n const publishResult = await this.httpClient.publishIpns(\n this.ipnsName,\n marshalledRecord,\n );\n\n if (!publishResult.success) {\n // Rollback version (sequence was not yet updated)\n this.dataVersion--;\n this.log(`IPNS publish failed: ${publishResult.error}`);\n return {\n success: false,\n error: publishResult.error ?? 'IPNS publish failed',\n timestamp: Date.now(),\n };\n }\n\n // Update local state\n this.ipnsSequenceNumber = newSeq;\n this.lastCid = cid;\n this.remoteCid = cid; // next save chains to this CID\n\n // Update cache\n this.cache.setIpnsRecord(this.ipnsName, {\n cid,\n sequence: newSeq,\n gateway: 'local',\n });\n this.cache.setContent(cid, updatedData as unknown as TxfStorageDataBase);\n this.cache.markIpnsFresh(this.ipnsName);\n\n // Persist state\n await this.statePersistence.save(this.ipnsName, {\n sequenceNumber: newSeq.toString(),\n lastCid: cid,\n version: this.dataVersion,\n });\n\n this.emitEvent({\n type: 'storage:saved',\n timestamp: Date.now(),\n data: { cid, sequence: newSeq.toString() },\n });\n\n this.log(`Saved: CID=${cid}, seq=${newSeq}`);\n return { success: true, cid, timestamp: Date.now() };\n } catch (error) {\n // Rollback version on any error\n this.dataVersion--;\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emitEvent({\n type: 'storage:error',\n timestamp: Date.now(),\n error: errorMessage,\n });\n return { success: false, error: errorMessage, timestamp: Date.now() };\n }\n }\n\n // ---------------------------------------------------------------------------\n // Write-behind buffer: scheduling and flushing\n // ---------------------------------------------------------------------------\n\n /**\n * Schedule a debounced background flush.\n * Resets the timer on each call so rapid mutations coalesce.\n */\n private scheduleFlush(): void {\n if (this.isShuttingDown) return;\n if (this.flushTimer) clearTimeout(this.flushTimer);\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n this.flushQueue.enqueue(() => this.executeFlush()).catch((err) => {\n this.log(`Background flush failed: ${err}`);\n });\n }, this.flushDebounceMs);\n }\n\n /**\n * Execute a flush of the pending buffer to IPFS.\n * Runs inside AsyncSerialQueue for concurrency safety.\n */\n private async executeFlush(): Promise<void> {\n if (this.pendingBuffer.isEmpty) return;\n\n // 1. Swap: take pending → active, create new empty pending\n const active = this.pendingBuffer;\n this.pendingBuffer = new WriteBuffer();\n\n try {\n // 2. Build the data to save\n // Use buffered TXF data if available, otherwise build minimal payload\n const baseData = (active.txfData ?? {\n _meta: { version: 0, address: this.identity?.directAddress ?? '', formatVersion: '2.0', updatedAt: 0 },\n }) as TData;\n\n // 3. Perform the actual blocking save\n const result = await this._doSave(baseData);\n\n if (!result.success) {\n throw new SphereError(result.error ?? 'Save failed', 'STORAGE_ERROR');\n }\n\n this.log(`Flushed successfully: CID=${result.cid}`);\n } catch (error) {\n // 4. Rollback: merge active back into pending\n this.pendingBuffer.mergeFrom(active);\n\n const msg = error instanceof Error ? error.message : String(error);\n this.log(`Flush failed (will retry): ${msg}`);\n\n // Schedule retry\n this.scheduleFlush();\n\n throw error; // re-throw so callers (e.g. shutdown) know it failed\n }\n }\n\n // ---------------------------------------------------------------------------\n // Load\n // ---------------------------------------------------------------------------\n\n async load(identifier?: string): Promise<LoadResult<TData>> {\n if (!this.ipnsName && !identifier) {\n return { success: false, error: 'Not initialized', source: 'local', timestamp: Date.now() };\n }\n\n this.emitEvent({ type: 'storage:loading', timestamp: Date.now() });\n\n try {\n // If a specific CID is given, fetch directly\n if (identifier) {\n const data = await this.httpClient.fetchContent<TData>(identifier);\n return { success: true, data, source: 'remote', timestamp: Date.now() };\n }\n\n const ipnsName = this.ipnsName!;\n\n // Check known-fresh cache\n if (this.cache.isIpnsKnownFresh(ipnsName)) {\n const cached = this.cache.getIpnsRecordIgnoreTtl(ipnsName);\n if (cached) {\n const content = this.cache.getContent(cached.cid);\n if (content) {\n this.log('Using known-fresh cached data');\n return { success: true, data: content as TData, source: 'cache', timestamp: Date.now() };\n }\n }\n }\n\n // Check IPNS cache (60s TTL)\n const cachedRecord = this.cache.getIpnsRecord(ipnsName);\n if (cachedRecord) {\n const content = this.cache.getContent(cachedRecord.cid);\n if (content) {\n this.log('IPNS cache hit');\n return { success: true, data: content as TData, source: 'cache', timestamp: Date.now() };\n }\n // Have CID but not content — fetch content\n try {\n const data = await this.httpClient.fetchContent<TData>(cachedRecord.cid);\n return { success: true, data, source: 'remote', timestamp: Date.now() };\n } catch {\n // Fall through to full resolution\n }\n }\n\n // Resolve IPNS from network\n const { best } = await this.httpClient.resolveIpns(ipnsName);\n\n if (!best) {\n // Not found — could be a new wallet\n this.log('IPNS record not found (new wallet?)');\n return { success: false, error: 'IPNS record not found', source: 'remote', timestamp: Date.now() };\n }\n\n // Track remote sequence and CID for chain validation\n if (best.sequence > this.lastKnownRemoteSequence) {\n this.lastKnownRemoteSequence = best.sequence;\n }\n this.remoteCid = best.cid;\n\n // Fetch content\n const data = await this.httpClient.fetchContent<TData>(best.cid);\n\n // Track remote version for correct version chaining\n const remoteVersion = (data as TxfStorageDataBase)?._meta?.version;\n if (typeof remoteVersion === 'number' && remoteVersion > this.dataVersion) {\n this.dataVersion = remoteVersion;\n }\n\n this.emitEvent({\n type: 'storage:loaded',\n timestamp: Date.now(),\n data: { cid: best.cid, sequence: best.sequence.toString() },\n });\n\n return { success: true, data, source: 'remote', timestamp: Date.now() };\n } catch (error) {\n // On network error, try to return cached data\n if (this.ipnsName) {\n const cached = this.cache.getIpnsRecordIgnoreTtl(this.ipnsName);\n if (cached) {\n const content = this.cache.getContent(cached.cid);\n if (content) {\n this.log('Network error, returning stale cache');\n return { success: true, data: content as TData, source: 'cache', timestamp: Date.now() };\n }\n }\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emitEvent({\n type: 'storage:error',\n timestamp: Date.now(),\n error: errorMessage,\n });\n return { success: false, error: errorMessage, source: 'remote', timestamp: Date.now() };\n }\n }\n\n // ---------------------------------------------------------------------------\n // Sync (enters serial queue to avoid concurrent IPNS conflicts)\n // ---------------------------------------------------------------------------\n\n async sync(localData: TData): Promise<SyncResult<TData>> {\n return this.flushQueue.enqueue(async () => {\n // Cancel any pending debounced flush (we'll save as part of sync)\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n\n this.emitEvent({ type: 'sync:started', timestamp: Date.now() });\n\n try {\n // Drain pending buffer — its data will be included via the sync save\n this.pendingBuffer.clear();\n\n // Load remote data\n const remoteResult = await this.load();\n\n if (!remoteResult.success || !remoteResult.data) {\n // No remote data — save local as initial\n this.log('No remote data found, uploading local data');\n const saveResult = await this._doSave(localData);\n this.emitEvent({ type: 'sync:completed', timestamp: Date.now() });\n return {\n success: saveResult.success,\n merged: localData,\n added: 0,\n removed: 0,\n conflicts: 0,\n error: saveResult.error,\n };\n }\n\n const remoteData = remoteResult.data;\n\n // Check if merge is needed\n const localVersion = localData._meta?.version ?? 0;\n const remoteVersion = remoteData._meta?.version ?? 0;\n\n if (localVersion === remoteVersion && this.lastCid) {\n // Same version — no merge needed\n this.log('Data is in sync (same version)');\n this.emitEvent({ type: 'sync:completed', timestamp: Date.now() });\n return {\n success: true,\n merged: localData,\n added: 0,\n removed: 0,\n conflicts: 0,\n };\n }\n\n // Merge\n this.log(`Merging: local v${localVersion} <-> remote v${remoteVersion}`);\n const { merged, added, removed, conflicts } = mergeTxfData(localData, remoteData);\n\n if (conflicts > 0) {\n this.emitEvent({\n type: 'sync:conflict',\n timestamp: Date.now(),\n data: { conflicts },\n });\n }\n\n // Save merged result\n const saveResult = await this._doSave(merged);\n\n this.emitEvent({\n type: 'sync:completed',\n timestamp: Date.now(),\n data: { added, removed, conflicts },\n });\n\n return {\n success: saveResult.success,\n merged: merged,\n added,\n removed,\n conflicts,\n error: saveResult.error,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emitEvent({\n type: 'sync:error',\n timestamp: Date.now(),\n error: errorMessage,\n });\n return {\n success: false,\n added: 0,\n removed: 0,\n conflicts: 0,\n error: errorMessage,\n };\n }\n });\n }\n\n // ---------------------------------------------------------------------------\n // Private Helpers\n // ---------------------------------------------------------------------------\n\n // ---------------------------------------------------------------------------\n // Optional Methods\n // ---------------------------------------------------------------------------\n\n async exists(): Promise<boolean> {\n if (!this.ipnsName) return false;\n\n // Check cache first\n const cached = this.cache.getIpnsRecord(this.ipnsName);\n if (cached) return true;\n\n // Resolve from network\n const { best } = await this.httpClient.resolveIpns(this.ipnsName);\n return best !== null;\n }\n\n async clear(): Promise<boolean> {\n if (!this.ipnsKeyPair || !this.ipnsName) return false;\n\n // Clear pending buffer\n this.pendingBuffer.clear();\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n\n const emptyData = {\n _meta: {\n version: 0,\n address: this.identity?.directAddress ?? '',\n ipnsName: this.ipnsName,\n formatVersion: '2.0',\n updatedAt: Date.now(),\n },\n } as TData;\n\n const result = await this._doSave(emptyData);\n if (result.success) {\n this.cache.clear();\n await this.statePersistence.clear(this.ipnsName);\n }\n return result.success;\n }\n\n onEvent(callback: StorageEventCallback): () => void {\n this.eventCallbacks.add(callback);\n return () => {\n this.eventCallbacks.delete(callback);\n };\n }\n\n // ---------------------------------------------------------------------------\n // Public Accessors\n // ---------------------------------------------------------------------------\n\n getIpnsName(): string | null {\n return this.ipnsName;\n }\n\n getLastCid(): string | null {\n return this.lastCid;\n }\n\n getSequenceNumber(): bigint {\n return this.ipnsSequenceNumber;\n }\n\n getDataVersion(): number {\n return this.dataVersion;\n }\n\n getRemoteCid(): string | null {\n return this.remoteCid;\n }\n\n // ---------------------------------------------------------------------------\n // Testing helper: wait for pending flush to complete\n // ---------------------------------------------------------------------------\n\n /**\n * Wait for the pending flush timer to fire and the flush operation to\n * complete. Useful in tests to await background writes.\n * Returns immediately if no flush is pending.\n */\n async waitForFlush(): Promise<void> {\n if (this.flushTimer) {\n // Force the timer to fire now\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n await this.flushQueue.enqueue(() => this.executeFlush()).catch((err) => {\n logger.warn('IPFS-Storage', 'Flush on shutdown failed:', err);\n });\n } else if (!this.pendingBuffer.isEmpty) {\n // No timer but pending data — flush now\n await this.flushQueue.enqueue(() => this.executeFlush()).catch((err) => {\n logger.warn('IPFS-Storage', 'Flush on shutdown failed:', err);\n });\n } else {\n // Ensure any in-flight flush completes\n await this.flushQueue.enqueue(async () => {});\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: Push Subscription Helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Derive WebSocket URL from the first configured gateway.\n * Converts https://host → wss://host/ws/ipns\n */\n private deriveWsUrl(): string | null {\n const gateways = this.httpClient.getGateways();\n if (gateways.length === 0) return null;\n\n const gateway = gateways[0];\n const wsProtocol = gateway.startsWith('https://') ? 'wss://' : 'ws://';\n const host = gateway.replace(/^https?:\\/\\//, '');\n return `${wsProtocol}${host}/ws/ipns`;\n }\n\n /**\n * Poll for remote IPNS changes (fallback when WS is unavailable).\n * Compares remote sequence number with last known and emits event if changed.\n */\n private async pollForRemoteChanges(): Promise<void> {\n if (!this.ipnsName) return;\n\n try {\n const { best } = await this.httpClient.resolveIpns(this.ipnsName);\n if (best && best.sequence > this.lastKnownRemoteSequence) {\n this.log(`Poll detected remote change: seq=${best.sequence} (was ${this.lastKnownRemoteSequence})`);\n this.lastKnownRemoteSequence = best.sequence;\n this.emitEvent({\n type: 'storage:remote-updated',\n timestamp: Date.now(),\n data: { name: this.ipnsName, sequence: Number(best.sequence), cid: best.cid },\n });\n }\n } catch {\n // Non-fatal — poll will retry on next interval\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private emitEvent(event: StorageEvent): void {\n for (const callback of this.eventCallbacks) {\n try {\n callback(event);\n } catch {\n // Don't let event handler errors break the provider\n }\n }\n }\n\n private log(message: string): void {\n logger.debug('IPFS-Storage', message);\n }\n\n}\n","/**\n * Browser IPFS State Persistence\n * Uses localStorage for persisting IPFS/IPNS state between sessions\n */\n\nimport type { IpfsStatePersistence, IpfsPersistedState } from '../../shared/ipfs';\n\nconst KEY_PREFIX = 'sphere_ipfs_';\n\nfunction seqKey(ipnsName: string): string {\n return `${KEY_PREFIX}seq_${ipnsName}`;\n}\n\nfunction cidKey(ipnsName: string): string {\n return `${KEY_PREFIX}cid_${ipnsName}`;\n}\n\nfunction verKey(ipnsName: string): string {\n return `${KEY_PREFIX}ver_${ipnsName}`;\n}\n\nexport class BrowserIpfsStatePersistence implements IpfsStatePersistence {\n async load(ipnsName: string): Promise<IpfsPersistedState | null> {\n try {\n const seq = localStorage.getItem(seqKey(ipnsName));\n if (!seq) return null;\n\n return {\n sequenceNumber: seq,\n lastCid: localStorage.getItem(cidKey(ipnsName)),\n version: parseInt(localStorage.getItem(verKey(ipnsName)) ?? '0', 10),\n };\n } catch {\n return null;\n }\n }\n\n async save(ipnsName: string, state: IpfsPersistedState): Promise<void> {\n try {\n localStorage.setItem(seqKey(ipnsName), state.sequenceNumber);\n if (state.lastCid) {\n localStorage.setItem(cidKey(ipnsName), state.lastCid);\n } else {\n localStorage.removeItem(cidKey(ipnsName));\n }\n localStorage.setItem(verKey(ipnsName), String(state.version));\n } catch {\n // localStorage might be full or unavailable\n }\n }\n\n async clear(ipnsName: string): Promise<void> {\n try {\n localStorage.removeItem(seqKey(ipnsName));\n localStorage.removeItem(cidKey(ipnsName));\n localStorage.removeItem(verKey(ipnsName));\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n","/**\n * Browser IPFS Storage Module\n * Factory function for browser-specific IPFS storage provider\n */\n\nimport { IpfsStorageProvider, type IpfsStorageConfig } from '../../shared/ipfs';\nimport { BrowserIpfsStatePersistence } from './browser-ipfs-state-persistence';\nimport type { IWebSocket } from '../../../transport/websocket';\n\n// Re-export for convenience\nexport { IpfsStorageProvider } from '../../shared/ipfs';\nexport { BrowserIpfsStatePersistence } from './browser-ipfs-state-persistence';\nexport type { IpfsStorageConfig as IpfsStorageProviderConfig } from '../../shared/ipfs';\n\n/**\n * Create a browser WebSocket that conforms to the IWebSocket interface.\n */\nfunction createBrowserWebSocket(url: string): IWebSocket {\n return new WebSocket(url) as unknown as IWebSocket;\n}\n\n/**\n * Create a browser IPFS storage provider with localStorage-based state persistence.\n * Automatically injects the browser WebSocket factory for IPNS push subscriptions.\n */\nexport function createBrowserIpfsStorageProvider(config?: IpfsStorageConfig): IpfsStorageProvider {\n return new IpfsStorageProvider(\n { ...config, createWebSocket: config?.createWebSocket ?? createBrowserWebSocket },\n new BrowserIpfsStatePersistence(),\n );\n}\n\n/** @deprecated Use createBrowserIpfsStorageProvider instead */\nexport const createIpfsStorageProvider = createBrowserIpfsStorageProvider;\n"],"mappings":"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","names":["elliptic","data","saveResult"]}