Files
wikideck/server/market/mainnet-custody-identity.ts
T

72 lines
3.8 KiB
TypeScript
Executable File

import type { Pool } from 'pg';
import { NETWORK } from '../../shared/network.ts';
import { CHAIN_ID } from '../testnet/consensus.ts';
import { CUSTODY_WRITE_LOCK, verifyCustodySchema } from './postgres-schema.ts';
export type MainnetCustodyIdentity = {
version: 1;
mode: 'mainnet-custody';
policy: 'custody-mainnet-v1';
network: 'wikideck-randomx-mainnet-1';
chainId: string;
id: string;
};
function uuid(value: string): boolean {
return /^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value);
}
/** One-time, schema-owner-only identity binding for the separately operated mainnet custody ledger. */
export async function initializeMainnetCustodyIdentity(pool: Pool, custodianId: string): Promise<MainnetCustodyIdentity> {
if (NETWORK !== 'wikideck-randomx-mainnet-1' || !uuid(custodianId)) {
throw new Error('Mainnet custody identity requires the mainnet profile and a valid custodian UUID');
}
const identity: MainnetCustodyIdentity = { version: 1, mode: 'mainnet-custody', policy: 'custody-mainnet-v1',
network: NETWORK, chainId: CHAIN_ID, id: custodianId };
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query("SET LOCAL statement_timeout='10s'; SET LOCAL lock_timeout='3s'; SET LOCAL synchronous_commit=on");
await client.query('SELECT pg_advisory_xact_lock($1::bigint)', [CUSTODY_WRITE_LOCK]);
await verifyCustodySchema(client);
const current = await client.query("SELECT value FROM wk_custody.custody_meta WHERE key='identity' FOR UPDATE");
if (current.rowCount) {
const parsed = JSON.parse(current.rows[0].value) as Partial<MainnetCustodyIdentity>;
if (parsed.version !== identity.version || parsed.mode !== identity.mode || parsed.policy !== identity.policy ||
parsed.network !== identity.network || parsed.chainId !== identity.chainId || parsed.id !== identity.id) {
throw new Error('Custody database is already bound to a different network or custodian identity');
}
} else {
const populated = await client.query(`SELECT
EXISTS(SELECT 1 FROM wk_custody.journals) AS has_journals,
EXISTS(SELECT 1 FROM wk_custody.monero_deposit_allocations) AS has_allocations`);
if (populated.rows[0].has_journals || populated.rows[0].has_allocations) {
throw new Error('Cannot initialize mainnet custody identity after financial or deposit records exist');
}
await client.query('INSERT INTO wk_custody.custody_meta(key,value) VALUES($1,$2)', ['identity', JSON.stringify(identity)]);
}
await client.query('COMMIT');
return identity;
} catch (error) { await client.query('ROLLBACK').catch(() => {}); throw error; }
finally { client.release(); }
}
export async function verifyMainnetCustodyIdentity(pool: Pool, custodianId: string): Promise<MainnetCustodyIdentity> {
if (NETWORK !== 'wikideck-randomx-mainnet-1' || !uuid(custodianId)) {
throw new Error('Mainnet custody identity requires the mainnet profile and a valid custodian UUID');
}
const result = await pool.query("SELECT value FROM wk_custody.custody_meta WHERE key='identity'");
if (result.rowCount !== 1) throw new Error('Mainnet custody identity is not initialized');
const identity = JSON.parse(result.rows[0].value) as Partial<MainnetCustodyIdentity>;
if (identity.version !== 1 || identity.mode !== 'mainnet-custody' || identity.policy !== 'custody-mainnet-v1' ||
identity.network !== NETWORK || identity.chainId !== CHAIN_ID || identity.id !== custodianId) {
throw new Error('Mainnet custody identity does not match configured custodian');
}
return identity as MainnetCustodyIdentity;
}
export function parseMainnetCustodianId(value: string): string {
if (!uuid(value)) throw new Error('Invalid mainnet custodian UUID');
return value.toLowerCase();
}