Burner: when a campaign's pinned position is dry on-chain, settle the spend from another funded position on the same account (credits are pooled per account)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,18 @@ const INFLIGHT_FILE = () => path.join(process.env.DATA_DIR || path.join(__dirnam
|
||||
function loadInflight() { try { return JSON.parse(fs.readFileSync(INFLIGHT_FILE(), 'utf8')); } catch (e) { return {}; } }
|
||||
function saveInflight(o) { try { fs.writeFileSync(INFLIGHT_FILE(), JSON.stringify(o)); } catch (e) {} }
|
||||
|
||||
let chain = null, ads = null;
|
||||
let chain = null, ads = null, accounts = null;
|
||||
// another of the same account's positions that can cover this burn on-chain (credits are pooled per account)
|
||||
async function fundedAlternative(b) {
|
||||
if (!accounts) return null;
|
||||
const owner = await ads.burnOwner(b.ref); if (!owner) return null;
|
||||
const acct = await accounts.byEmail(owner); if (!acct) return null;
|
||||
const ids = [acct.memberId, ...(await accounts.positions(owner)).map(p => p.memberId)].filter(id => id && id !== b.memberId);
|
||||
for (const id of [...new Set(ids)]) {
|
||||
try { const bal = await chain.creditBalance(id, 0); const held = await ads.unburnedFor(id); if (bal - held >= b.amount) return id; } catch (e) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const ABI = ['function consume(uint32 memberId_, uint8 creditType, uint256 amount, bytes32 campaignRef)', 'function engineSigner() view returns (address)'];
|
||||
const state = { enabled: false, address: null, signer: null, balanceWei: '0', lastRun: 0, lastError: null, burned: 0, lastTx: null, mismatch: false, skipped: {} };
|
||||
let running = false;
|
||||
@@ -94,7 +105,15 @@ async function tick() {
|
||||
// dry-run first: a revert here (usually "Insufficient credits", the member's on-chain
|
||||
// balance is below what the engine metered) costs no gas and is left for the admin
|
||||
try { await ctr.consume.staticCall(Number(b.memberId), 0, BigInt(b.amount), refToBytes32(b.id)); }
|
||||
catch (e) { state.skipped[b.id] = String(e.reason || e.shortMessage || e.message).slice(0, 120); continue; }
|
||||
catch (e) {
|
||||
const why = String(e.reason || e.shortMessage || e.message).slice(0, 120);
|
||||
// the pinned position is dry: settle from another funded position on the same account
|
||||
const alt = /insufficient credits/i.test(why) ? await fundedAlternative(b) : null;
|
||||
if (!alt) { state.skipped[b.id] = why + (alt === null && /insufficient credits/i.test(why) ? ' (no funded position on the account)' : ''); continue; }
|
||||
try { await ctr.consume.staticCall(Number(alt), 0, BigInt(b.amount), refToBytes32(b.id)); }
|
||||
catch (e2) { state.skipped[b.id] = String(e2.reason || e2.shortMessage || e2.message).slice(0, 120); continue; }
|
||||
await ads.reassignBurn(b.id, alt); b.memberId = alt; delete state.skipped[b.id];
|
||||
}
|
||||
const g = await chain.suggestedFees();
|
||||
const overrides = { gasLimit: 120000n, maxPriorityFeePerGas: BigInt(g.maxPriorityFeePerGas), maxFeePerGas: BigInt(g.maxFeePerGas) };
|
||||
state.inflight[b.id] = Date.now(); saveInflight(state.inflight);
|
||||
@@ -115,5 +134,5 @@ async function tick() {
|
||||
return { burned };
|
||||
}
|
||||
function status() { return Object.assign({}, state, { hasEthers: !!ethers, keyPresent: !!keyHex() }); }
|
||||
function init(opts) { chain = opts.chain; ads = opts.ads; setup().catch(e => { state.lastError = e.message; }); }
|
||||
function init(opts) { chain = opts.chain; ads = opts.ads; accounts = opts.accounts || null; setup().catch(e => { state.lastError = e.message; }); }
|
||||
module.exports = { init, tick, status };
|
||||
|
||||
Reference in New Issue
Block a user