Fix: relay tx receipts through the server (CSP blocked direct RPC polls)

After a wallet sent a transaction, waitTx polled the public RPC straight
from the browser, which connect-src 'self' blocks (Firefox NetworkError,
reported by Marty on Activate; the activation itself landed on-chain).
New /api/tx/<hash> relays eth_getTransactionReceipt via the server's RPC
pool, so the browser only ever talks to us and the mainnet flip needs no
CSP changes. Assets v=20260905a.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-05 05:40:56 -05:00
parent f848112e2d
commit 013a88b7d0
6 changed files with 29 additions and 22 deletions
+3 -4
View File
@@ -49,11 +49,10 @@ window.IAPWallet = (function () {
return eth().request({ method: 'eth_sendTransaction', params: [tx] });
}
async function waitTx(hash) {
const c = await IAP.getConfig();
// poll our own server (CSP-friendly); it relays the receipt from the RPC
for (let i = 0; i < 60; i++) {
const r = await (await fetch(c.rpc, { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_getTransactionReceipt', params: [hash] }) })).json();
if (r.result) return r.result;
const r = await (await fetch('/api/tx/' + hash)).json();
if (r.found) return { status: r.status, blockNumber: r.blockNumber };
await new Promise(res => setTimeout(res, 2500));
}
throw new Error('Timed out waiting for the transaction. Check the explorer.');
+4 -4
View File
@@ -5,7 +5,7 @@
<title>The contract | InstantAdPay</title>
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260904r">
<link rel="stylesheet" href="/assets/site.css?v=20260905a">
</head>
<body>
<div class="wrap">
@@ -129,8 +129,8 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260904r"></script>
<script src="/assets/contract.js?v=20260904r"></script>
<script src="/assets/chat.js?v=20260904r"></script>
<script src="/assets/common.js?v=20260905a"></script>
<script src="/assets/contract.js?v=20260905a"></script>
<script src="/assets/chat.js?v=20260905a"></script>
</body>
</html>
+5 -5
View File
@@ -5,7 +5,7 @@
<title>InstantAdPay: advertise and earn, locked in code</title>
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260904r">
<link rel="stylesheet" href="/assets/site.css?v=20260905a">
</head>
<body>
@@ -398,9 +398,9 @@
</div>
</section>
<script src="/assets/common.js?v=20260904r"></script>
<script src="/assets/wallet.js?v=20260904r"></script>
<script src="/assets/home.js?v=20260904r"></script>
<script src="/assets/chat.js?v=20260904r"></script>
<script src="/assets/common.js?v=20260905a"></script>
<script src="/assets/wallet.js?v=20260905a"></script>
<script src="/assets/home.js?v=20260905a"></script>
<script src="/assets/chat.js?v=20260905a"></script>
</body>
</html>
+4 -4
View File
@@ -5,7 +5,7 @@
<title>Live ledger | InstantAdPay</title>
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260904r">
<link rel="stylesheet" href="/assets/site.css?v=20260905a">
</head>
<body>
<div class="wrap">
@@ -25,8 +25,8 @@
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
</footer>
</div>
<script src="/assets/common.js?v=20260904r"></script>
<script src="/assets/ledger.js?v=20260904r"></script>
<script src="/assets/chat.js?v=20260904r"></script>
<script src="/assets/common.js?v=20260905a"></script>
<script src="/assets/ledger.js?v=20260905a"></script>
<script src="/assets/chat.js?v=20260905a"></script>
</body>
</html>
+5 -5
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260904r">
<link rel="stylesheet" href="/assets/site.css?v=20260905a">
</head>
<body class="bo-body">
@@ -208,9 +208,9 @@
</div>
</div>
<script src="/assets/common.js?v=20260904r"></script>
<script src="/assets/wallet.js?v=20260904r"></script>
<script src="/assets/my.js?v=20260904r"></script>
<script src="/assets/chat.js?v=20260904r"></script>
<script src="/assets/common.js?v=20260905a"></script>
<script src="/assets/wallet.js?v=20260905a"></script>
<script src="/assets/my.js?v=20260905a"></script>
<script src="/assets/chat.js?v=20260905a"></script>
</body>
</html>
+8
View File
@@ -173,6 +173,14 @@ const server = http.createServer(async (req, res) => {
req.on('close', () => feedClients.delete(res));
return;
}
m = /^\/api\/tx\/(0x[0-9a-fA-F]{64})$/.exec(p);
if (m && req.method === 'GET') {
// receipt relay so the browser never talks to the RPC directly (CSP stays 'self')
try {
const r = await chain.rpc('eth_getTransactionReceipt', [m[1]]);
return json(res, 200, r ? { found: true, status: r.status, blockNumber: r.blockNumber } : { found: false });
} catch (e) { return json(res, 200, { found: false, rpcError: true }); }
}
if (p === '/api/sponsor' && req.method === 'GET') {
const tok = parseCookies(req)['iap.sponsor'] || '';
const sponsorId = await resolveSponsorToken(tok);