Mainnet indexer fix + retire testnet copy
- chain.js: derive the chain tip as the MAX height across the RPC pool and scan getLogs against that same synced node. Load-balanced public RPCs (publicnode) intermittently answer eth_blockNumber from a replica lagging thousands of blocks behind, which stalled the mainnet scan (latest < lastBlock, 0 events) and could skip freshly-mined events. This unblocks the live ledger + the purchase-confirmation email (both driven by the event scan). - disclaimer: replace the "rehearsal/testnet" section with a live-on-Polygon real-money notice (it's live now). - Point the dead Amoy fallbacks (purchase email tx link, contract-page source link) at Polygon mainnet / the verified mainnet contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -189,15 +189,36 @@ function saveState() {
|
|||||||
fs.renameSync(tmp, STATE_FILE);
|
fs.renameSync(tmp, STATE_FILE);
|
||||||
} catch (e) { console.error('chain state save failed', e.message); }
|
} catch (e) { console.error('chain state save failed', e.message); }
|
||||||
}
|
}
|
||||||
|
// Most-synced tip across the RPC pool. Public load-balanced nodes (publicnode)
|
||||||
|
// sometimes answer eth_blockNumber from a replica lagging thousands of blocks
|
||||||
|
// behind — which stalls the scan (latest < lastBlock) and, worse, could let a
|
||||||
|
// getLogs from a lagging node skip freshly-mined events for good. Take the MAX
|
||||||
|
// height and remember which node reported it, so we scan against a node we know
|
||||||
|
// is synced to that height.
|
||||||
|
async function bestTip() {
|
||||||
|
const urls = getConfig().rpcs;
|
||||||
|
let best = { block: 0, url: urls[0] };
|
||||||
|
await Promise.all(urls.map(async u => {
|
||||||
|
try { const b = parseInt(await rpcOnce(u, 'eth_blockNumber', []), 16);
|
||||||
|
if (b > best.block) best = { block: b, url: u }; } catch (e) {}
|
||||||
|
}));
|
||||||
|
return best;
|
||||||
|
}
|
||||||
async function tail() {
|
async function tail() {
|
||||||
if (busy) return; busy = true;
|
if (busy) return; busy = true;
|
||||||
try {
|
try {
|
||||||
const latest = parseInt(await rpc('eth_blockNumber', []), 16);
|
const tip = await bestTip();
|
||||||
|
const latest = tip.block;
|
||||||
while (state.lastBlock < latest) {
|
while (state.lastBlock < latest) {
|
||||||
const from = state.lastBlock + 1;
|
const from = state.lastBlock + 1;
|
||||||
const to = Math.min(from + CHUNK - 1, latest);
|
const to = Math.min(from + CHUNK - 1, latest);
|
||||||
const logs = await rpc('eth_getLogs', [{ address: getConfig().contract,
|
// scan against the node we confirmed is synced to `latest`; fall back to
|
||||||
fromBlock: '0x' + from.toString(16), toBlock: '0x' + to.toString(16) }]);
|
// the rotating pool only if that specific node errors on this range.
|
||||||
|
let logs;
|
||||||
|
try { logs = await rpcOnce(tip.url, 'eth_getLogs', [{ address: getConfig().contract,
|
||||||
|
fromBlock: '0x' + from.toString(16), toBlock: '0x' + to.toString(16) }]); }
|
||||||
|
catch (e) { logs = await rpc('eth_getLogs', [{ address: getConfig().contract,
|
||||||
|
fromBlock: '0x' + from.toString(16), toBlock: '0x' + to.toString(16) }]); }
|
||||||
for (const lg of logs) {
|
for (const lg of logs) {
|
||||||
const ev = decodeLog(lg);
|
const ev = decodeLog(lg);
|
||||||
if (!ev) continue;
|
if (!ev) continue;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
$('lnkSource').href = src;
|
$('lnkSource').href = src;
|
||||||
$('lnkSource2').href = src;
|
$('lnkSource2').href = src;
|
||||||
} else {
|
} else {
|
||||||
// private rehearsal chain: point at the audited Amoy verification instead
|
// fallback if explorer unset: point straight at the Sourcify-verified source
|
||||||
const src = 'https://repo.sourcify.dev/contracts/full_match/80002/0xaF47EC401A9B0D570a553b1A1c95D6079083636e/';
|
const src = 'https://repo.sourcify.dev/contracts/full_match/137/0xBE1ECA72AFF47d13D8E907e523F55eB9e2d365E0/';
|
||||||
$('lnkExplorer').href = src;
|
$('lnkExplorer').href = src;
|
||||||
$('lnkSource').href = src;
|
$('lnkSource').href = src;
|
||||||
$('lnkSource2').href = src;
|
$('lnkSource2').href = src;
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="/assets/common.js?v=20260908c"></script>
|
<script src="/assets/common.js?v=20260908c"></script>
|
||||||
<script src="/assets/contract.js?v=20260906m"></script>
|
<script src="/assets/contract.js?v=20260908p"></script>
|
||||||
<script src="/assets/chat.js?v=20260906m"></script>
|
<script src="/assets/chat.js?v=20260906m"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
<p class="muted small">Nothing on the Platform is investment, financial, tax, or legal advice. Do your own research and consult a professional before spending money.</p>
|
<p class="muted small">Nothing on the Platform is investment, financial, tax, or legal advice. Do your own research and consult a professional before spending money.</p>
|
||||||
<h3>Crypto risk</h3>
|
<h3>Crypto risk</h3>
|
||||||
<p class="muted small">Purchases settle on a public blockchain using a volatile network token. On-chain transactions are final and irreversible: there are no refunds, chargebacks, or reversals. You are responsible for your wallet, your keys, network fees, and the tax treatment of your activity.</p>
|
<p class="muted small">Purchases settle on a public blockchain using a volatile network token. On-chain transactions are final and irreversible: there are no refunds, chargebacks, or reversals. You are responsible for your wallet, your keys, network fees, and the tax treatment of your activity.</p>
|
||||||
<h3>Rehearsal / testnet</h3>
|
<h3>Live on Polygon</h3>
|
||||||
<p class="muted small">During any rehearsal or testnet phase, the Platform runs on test tokens with no monetary value. Balances, purchases, and payouts during that phase are for testing only and do not carry over to a live launch.</p>
|
<p class="muted small">The Platform is live on the Polygon mainnet. Purchases and payouts use POL, a real cryptocurrency with real monetary value. Every transaction is real and final — this is not a simulation or test environment.</p>
|
||||||
<h3>Advertising</h3>
|
<h3>Advertising</h3>
|
||||||
<p class="muted small">Ads are member-created and auto-approved for speed. InstantAdPay does not endorse and is not responsible for advertised products, sites, or claims. Use your judgment, and report anything broken or inappropriate.</p>
|
<p class="muted small">Ads are member-created and auto-approved for speed. InstantAdPay does not endorse and is not responsible for advertised products, sites, or claims. Use your judgment, and report anything broken or inappropriate.</p>
|
||||||
<h3>Your responsibility</h3>
|
<h3>Your responsibility</h3>
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ async function emailOnEvent(ev) {
|
|||||||
};
|
};
|
||||||
if (ev.type === 'Purchase') {
|
if (ev.type === 'Purchase') {
|
||||||
const cc = chain.getConfig();
|
const cc = chain.getConfig();
|
||||||
const txUrl = (cc.explorer ? cc.explorer.replace(/\/+$/, '') : 'https://amoy.polygonscan.com') + '/tx/' + ev.tx;
|
const txUrl = (cc.explorer ? cc.explorer.replace(/\/+$/, '') : 'https://polygonscan.com') + '/tx/' + ev.tx;
|
||||||
let bal = ev.creditAmount;
|
let bal = ev.creditAmount;
|
||||||
try { bal = await chain.creditBalance(ev.buyerId, ev.creditType); } catch (e) {}
|
try { bal = await chain.creditBalance(ev.buyerId, ev.creditType); } catch (e) {}
|
||||||
await notify(ev.buyerId, 'Your InstantAdPay purchase is confirmed',
|
await notify(ev.buyerId, 'Your InstantAdPay purchase is confirmed',
|
||||||
|
|||||||
Reference in New Issue
Block a user