Credits: reserve live campaign budgets so purchased credits can't be over-committed; burner dry-runs and skips reverting burns; coaching flags directs not bound to you on-chain

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-10 07:50:59 -05:00
parent 9ebb9a86b2
commit 7374dc8f1b
5 changed files with 15 additions and 6 deletions
+7 -3
View File
@@ -7,7 +7,7 @@ const fs = require('fs');
let chain = null, ads = 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 };
const state = { enabled: false, address: null, signer: null, balanceWei: '0', lastRun: 0, lastError: null, burned: 0, lastTx: null, mismatch: false, skipped: {} };
let running = false;
function keyHex() {
@@ -53,6 +53,10 @@ async function tick() {
try {
// Polygon nodes reject low priority fees and some public RPCs answer fee
// queries with 500s: set the fees ourselves from the server's estimate
// 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.ref)); }
catch (e) { state.skipped[b.id] = String(e.reason || e.shortMessage || e.message).slice(0, 120); continue; }
const g = await chain.suggestedFees();
const overrides = { gasLimit: 120000n, maxPriorityFeePerGas: BigInt(g.maxPriorityFeePerGas), maxFeePerGas: BigInt(g.maxFeePerGas) };
const tx = await ctr.consume(Number(b.memberId), 0, BigInt(b.amount), refToBytes32(b.ref), overrides);
@@ -64,8 +68,8 @@ async function tick() {
// a node error (500, timeout, rate limit): move to the next RPC for the next tick.
// an "Insufficient credits" revert means the member's on-chain balance is
// already lower than the engine thinks; leave it pending for the admin to review
if (/server response|timeout|rate|429|503|502|500/i.test(String(e.message))) rotateRpc();
break;
if (/server response|timeout|rate|429|503|502|500/i.test(String(e.message))) { rotateRpc(); break; }
state.skipped[b.id] = String(e.reason || e.shortMessage || e.message).slice(0, 120);
}
}
} finally { running = false; }