File size: 4,894 Bytes
a18e5eb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | /**
* Settlement Engine β SEALFORGE β FORGE token
*
* Reads ALL WORM entries, filters to type === 'event', derives a bytes32
* wormHash from each chainHash, checks the contract for prior consumption,
* and calls wormMint for any un-minted entries.
*
* Settlement state is persisted to ./data/settlement-state.json as an
* append-only record. Processed entries are NEVER modified or deleted.
*
* NEXUS β Orchestration Agent (connecting SEALFORGE Node 1 β FORGE Node 2)
*/
import * as fs from 'fs'
import * as path from 'path'
import { ethers } from 'ethers'
import { WormLog, WormEntry } from '../storage/worm-log'
import { ForgeContract } from './forge-contract'
import { toBytes32 } from './bytes32'
// 0.01 FRG per settled WORM event (testnet rate)
const BASE_EMISSION = ethers.parseEther('0.01')
const BASE_EMISSION_STR = '0.01'
/** A record of a successful (or attempted) settlement. */
export interface SettlementRecord {
wormEntryId: string
wormHash: string
txHash: string
recipient: string
amountEther: string
settledAt: string
}
/** Loaded at startup; keyed by wormEntryId for O(1) de-dupe. */
export interface SettlementState {
processed: Record<string, SettlementRecord>
}
function getStateFilePath(): string {
const dataDir = process.env.DATA_DIR ?? './data'
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
return path.join(dataDir, 'settlement-state.json')
}
function loadState(): SettlementState {
const fp = getStateFilePath()
if (!fs.existsSync(fp)) return { processed: {} }
try {
const raw = fs.readFileSync(fp, 'utf8')
return JSON.parse(raw) as SettlementState
} catch {
console.error('[NEXUS:SETTLEMENT] Could not parse settlement-state.json β starting fresh')
return { processed: {} }
}
}
function saveState(state: SettlementState): void {
const fp = getStateFilePath()
fs.writeFileSync(fp, JSON.stringify(state, null, 2), 'utf8')
}
export interface SettlementRunResult {
checked: number
minted: number
skipped: number
errors: number
txHashes: string[]
}
/**
* Run one pass of the settlement engine.
* Returns a summary of the run.
*/
export async function runSettlement(
worm: WormLog,
forge: ForgeContract,
): Promise<SettlementRunResult> {
const state = loadState()
const recipient = process.env.MINT_RECIPIENT
if (!recipient) throw new Error('[NEXUS:SETTLEMENT] MINT_RECIPIENT env var is required')
// Pull all entries β use a large limit; production should paginate if needed
const { entries, chainValid } = worm.getAll(100_000, 0)
if (!chainValid) throw new Error('[NEXUS:SETTLEMENT] WORM chain integrity failure β aborting')
// Only settle 'event' type entries β skip lifecycle noise
const events: WormEntry[] = entries.filter(e => e.type === 'event')
const result: SettlementRunResult = {
checked: events.length,
minted: 0,
skipped: 0,
errors: 0,
txHashes: [],
}
for (const entry of events) {
// Already settled locally β skip without hitting the RPC
if (state.processed[entry.id]) {
result.skipped++
continue
}
const wormHash = toBytes32(entry.chainHash)
try {
// Check on-chain β skip if already consumed
const consumed = await forge.isConsumed(wormHash)
if (consumed) {
console.log(`[NEXUS:SETTLEMENT] SKIP (on-chain consumed) β entry ${entry.id}`)
// Record locally so we don't re-check next cycle
const record: SettlementRecord = {
wormEntryId: entry.id,
wormHash,
txHash: 'ALREADY_CONSUMED',
recipient,
amountEther: BASE_EMISSION_STR,
settledAt: new Date().toISOString(),
}
state.processed[entry.id] = record
result.skipped++
continue
}
console.log(`[NEXUS:SETTLEMENT] MINT β entry ${entry.id} β ${recipient}`)
const txHash = await forge.mint(recipient, BASE_EMISSION_STR, wormHash)
console.log(`[NEXUS:SETTLEMENT] TX ${txHash} confirmed`)
const record: SettlementRecord = {
wormEntryId: entry.id,
wormHash,
txHash,
recipient,
amountEther: BASE_EMISSION_STR,
settledAt: new Date().toISOString(),
}
// Append-only β write after each successful mint so a crash mid-run
// doesn't lose prior confirmations
state.processed[entry.id] = record
saveState(state)
result.minted++
result.txHashes.push(txHash)
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err)
console.error(`[NEXUS:SETTLEMENT] ERROR on entry ${entry.id}: ${msg}`)
result.errors++
// Continue to next entry β never let one failure stop the run
}
}
// Persist final state (covers the skipped-but-recorded entries)
saveState(state)
return result
}
|