// Pure trusted-mint registry transitions. Persistence serializes these // operations, while this module keeps pinning and backup policy auditable. import type {TrustedMint, TrustedMintNodeInfo, TrustKeyResult} from './trustedMints' const PUBKEY_PATTERN = /^[0-9a-f]{66}$/ export const isValidMintPubkey = (value: string): boolean => PUBKEY_PATTERN.test(value.toLowerCase()) export type MintTransition = { readonly mints: TrustedMint[] readonly result: T readonly changed: boolean } type MintKeyInput = { readonly server: string readonly mintPubkey: string } type AddMintInput = MintKeyInput & { readonly nodeInfo?: TrustedMintNodeInfo } const unchanged = (mints: TrustedMint[], result: T): MintTransition => ({ mints, result, changed: false, }) const changed = (mints: TrustedMint[], result: T): MintTransition => ({ mints, result, changed: true, }) export const lockMint = ( mints: TrustedMint[], input: MintKeyInput, ): MintTransition => { const key = input.mintPubkey.trim().toLowerCase() if (!input.server || !isValidMintPubkey(key)) { return unchanged(mints, 'unchanged') } const existing = mints.find((mint) => mint.server === input.server) if (!existing) { return changed( [ ...mints, { server: input.server, mintPubkey: key, addedAt: Date.now(), locked: true, }, ], 'added', ) } if (existing.mintPubkey === key) { if (existing.locked && !existing.unconfirmed) { return unchanged(mints, 'unchanged') } return changed( mints.map((mint) => mint.server === input.server ? {...mint, locked: true, unconfirmed: undefined} : mint, ), 'unchanged', ) } if (existing.pendingMintPubkey === key) { return unchanged(mints, 'rekey-pending') } return changed( mints.map((mint) => (mint.server === input.server ? {...mint, pendingMintPubkey: key} : mint)), 'rekey-pending', ) } export const grandfatherMint = ( mints: TrustedMint[], input: MintKeyInput, ): MintTransition => { const key = input.mintPubkey.trim().toLowerCase() if (!input.server || !isValidMintPubkey(key)) { return unchanged(mints, 'unchanged') } const existing = mints.find((mint) => mint.server === input.server) if (!existing) { return changed( [ ...mints, { server: input.server, mintPubkey: key, addedAt: Date.now(), locked: false, unconfirmed: true, }, ], 'added', ) } if (existing.mintPubkey === key) return unchanged(mints, 'unchanged') if (existing.pendingMintPubkey === key) { return unchanged(mints, 'rekey-pending') } return changed( mints.map((mint) => (mint.server === input.server ? {...mint, pendingMintPubkey: key} : mint)), 'rekey-pending', ) } export const addMint = ( mints: TrustedMint[], input: AddMintInput, ): MintTransition => { const server = input.server.trim() const key = input.mintPubkey.trim().toLowerCase() if (!server) throw new Error('Enter a server.') if (!isValidMintPubkey(key)) { throw new Error('Signing key must be a 33-byte compressed pubkey (66 hex characters).') } const existing = mints.find((mint) => mint.server === server) if (!existing) { return changed( [ ...mints, { server, mintPubkey: key, addedAt: Date.now(), locked: false, ...input.nodeInfo, }, ], 'added', ) } if (existing.mintPubkey === key) { return changed( mints.map((mint) => mint.server === server ? {...mint, ...input.nodeInfo, unconfirmed: undefined} : mint, ), 'unchanged', ) } return changed( mints.map((mint) => mint.server === server ? {...mint, pendingMintPubkey: key, ...input.nodeInfo} : mint, ), 'rekey-pending', ) } export const confirmMintRekey = (mints: TrustedMint[], server: string): MintTransition => { const pending = mints.find((mint) => mint.server === server)?.pendingMintPubkey if (!pending) return unchanged(mints, undefined) return changed( mints.map((mint) => mint.server === server ? { ...mint, mintPubkey: pending, pendingMintPubkey: undefined, unconfirmed: undefined, } : mint, ), undefined, ) } export const dismissMintRekey = (mints: TrustedMint[], server: string): MintTransition => { if (!mints.some((mint) => mint.server === server)) { return unchanged(mints, undefined) } return changed( mints.map((mint) => (mint.server === server ? {...mint, pendingMintPubkey: undefined} : mint)), undefined, ) } export const cacheMintNodeInfo = ( mints: TrustedMint[], server: string, nodeInfo: TrustedMintNodeInfo, ): MintTransition => { if (!mints.some((mint) => mint.server === server)) { return unchanged(mints, undefined) } return changed( mints.map((mint) => (mint.server === server ? {...mint, ...nodeInfo} : mint)), undefined, ) } export const removeMint = (mints: TrustedMint[], server: string): MintTransition => { const existing = mints.find((mint) => mint.server === server) if (!existing) return unchanged(mints, undefined) if (existing.locked) { throw new Error("Can't remove - you hold a bearer note from this mint.") } return changed( mints.filter((mint) => mint.server !== server), undefined, ) } export const clearMints = (): MintTransition => changed([], undefined)