/** One-shot ledger re-sync: reconcile positions.json against on-chain CTF balances.
* Fixes drift where positions were falsely marked 'closed' (e.g. by an old reconcile that
* trusted the flaky Data API): any closed record the deposit wallet STILL holds on-chain
* is reopened. Read-only against the chain; only mutates the local ledger.
*
* Usage: [POSITIONS=/path/positions.json] [DEPOSIT_WALLET=0x..] [DRY=1] \
* node --experimental-global-webcrypto resync-ledger.mjs */
import { createPublicClient, http } from 'viem';
import { polygon } from 'viem/chains';
import fs from 'node:fs';
const pub = createPublicClient({ chain: polygon, transport: http(process.env.RPC || 'https://polygon.drpc.org') });
const CTF = '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045';
const DW = process.env.DEPOSIT_WALLET || '0x50A8061e9448EB1e5d5e7aF07BE4E4F63C6F24Ff';
const BAL_ABI = [{ name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }, { name: 'id', type: 'uint256' }], outputs: [{ type: 'uint256' }] }];
const DRY = process.env.DRY === '1';
const f = process.env.POSITIONS || './positions.json';
const d = JSON.parse(fs.readFileSync(f, 'utf8'));
let reopened = 0;
for (const [k, p] of Object.entries(d)) {
if (p.status === 'open') continue;
let b = 0n;
try { b = await pub.readContract({ address: CTF, abi: BAL_ABI, functionName: 'balanceOf', args: [DW, BigInt(k)] }); }
catch (e) { console.log(' rpc err', k.slice(0, 10), e.message); continue; }
if (b > 0n) {
console.log(` GHOST ${(Number(b) / 1e6).toFixed(0)} sh | ${String(p.title).slice(0, 36)}`);
if (!DRY) { p.status = 'open'; delete p.closedReason; delete p.realizedPnl; delete p.closedAt; }
reopened++;
}
}
if (!DRY) fs.writeFileSync(f, JSON.stringify(d, null, 1));
console.log(`${DRY ? 'DRY — would reopen' : 'reopened'}: ${reopened} | open now: ${Object.values(d).filter((p) => p.status === 'open').length}`);