72 lines
3.8 KiB
TypeScript
Executable File
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();
|
|
}
|