My line: POL earned from each person; wallet picker features non-Trust wallets first

- /api/my/line sums TierPaid events per buyer for the signed-in member; the
  downline rows show "+X POL" (what that person has paid you so far).
- AppKit featuredWalletIds: MetaMask, Phantom, SafePal, Coinbase first; Trust
  Wallet stays available under All wallets.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-09 07:47:41 -05:00
parent 9608d07544
commit c7018f6258
6 changed files with 33 additions and 8 deletions
+2
View File
@@ -949,6 +949,8 @@
'<div class="lin-lvl"><div class="cap">Level ' + L.level + ' · ' + L.members.length + (L.level === 1 ? ' direct' : '') + '</div>'
+ L.members.map(m => '<div class="lin-row"><span class="nm">' + esc(m.name) + '</span>'
+ (m.email ? '<span class="em">' + esc(m.email) + '</span>' : '<span class="id">#' + m.memberId + '</span>')
+ '<span class="earn' + (m.earnedWei && m.earnedWei !== '0' ? ' on' : '') + '" title="POL this person has paid you so far">'
+ (m.earnedWei && m.earnedWei !== '0' ? '+' + IAP.fmtPol(m.earnedWei) + ' POL' : '0.00 POL') + '</span>'
+ '<span class="dt">' + new Date(m.joined).toLocaleDateString() + '</span>'
+ (m.email ? '<button class="btn sec small chat-msg-btn" type="button" data-cemail="' + esc(m.email) + '" data-cname="' + esc(m.name) + '">Message</button>' : '')
+ '</div>').join('')
+4 -1
View File
@@ -406,7 +406,7 @@ textarea{resize:vertical;font:inherit}
.lin-lvl{margin:10px 0}
.lin-lvl>.cap{display:inline-block;font-size:13px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;
color:var(--mint);background:var(--mint-soft);border:1px solid var(--line-strong);border-radius:8px;padding:4px 10px;margin-bottom:10px}
.lin-row{display:grid;grid-template-columns:minmax(110px,1.2fr) minmax(0,2fr) auto auto;gap:6px 14px;
.lin-row{display:grid;grid-template-columns:minmax(110px,1.2fr) minmax(0,2fr) auto auto auto;gap:6px 14px;
align-items:center;padding:9px 0;border-bottom:1px solid var(--line)}
.lin-row .nm{font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lin-row .em{font-size:12px;color:var(--mint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@@ -606,3 +606,6 @@ img{max-width:100%}
.obj .lab-say{color:var(--amber)}
.obj-truth p{margin:0;font-size:14.5px;color:var(--ink)}
.obj-say{margin:0}
.lin-row .earn{font-family:var(--mono);font-size:12px;color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.lin-row .earn.on{color:var(--mint);font-weight:700}
+10 -1
View File
@@ -49,7 +49,16 @@ window.IAPWallet = (function () {
adapters: [wagmiAdapter], networks: allNets, projectId, defaultNetwork: net,
metadata: { name: c.siteName || 'InstantAdPay', description: c.tagline || 'Advertise and earn, paid on-chain.',
url: location.origin, icons: [location.origin + '/logo-icon.png'] },
features: { analytics: false, email: false, socials: [] }
features: { analytics: false, email: false, socials: [] },
// Picker order: wallets without Trust's balance-proportion block go first.
// Trust Wallet is NOT excluded; it just drops out of the featured row into
// "All wallets" (Marty, 2026-09-09: move Trust to the bottom, not off).
featuredWalletIds: [
'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96', // MetaMask
'a797aa35c0fadbfc1a53e7f675162ed5226968b44a19ee3d24385c64d1d3c393', // Phantom
'0b415a746fb9ee99cce155c2ceca0c6f6061b1dbca2d722b3ba16381d0562150', // SafePal
'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa' // Coinbase Wallet
]
});
return modal;
})();
+1 -1
View File
@@ -439,7 +439,7 @@
</section>
<script src="/assets/common.js?v=20260909a"></script>
<script src="/assets/wallet.js?v=20260909b"></script>
<script src="/assets/wallet.js?v=20260909c"></script>
<script src="/assets/home.js?v=20260906m"></script>
<script src="/assets/chat.js?v=20260906m"></script>
</body>
+4 -4
View File
@@ -5,7 +5,7 @@
<title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260909a">
<link rel="stylesheet" href="/assets/site.css?v=20260909b">
</head>
<body class="bo-body">
@@ -283,7 +283,7 @@
<div class="card">
<h3>Your downline, three levels deep</h3>
<p class="muted small">Everyone below you across levels 1–3. You see email addresses for your
<p class="muted small">Everyone below you across levels 1–3. The POL figure is what each person has paid you so far, straight from the ledger. You see email addresses for your
direct referrals only; deeper levels show username and ID. Message your direct referrals right here.</p>
<div id="lineageWrap"><p class="muted small">Loading…</p></div>
</div>
@@ -737,9 +737,9 @@
</div>
</div>
<script src="/assets/common.js?v=20260909a"></script>
<script src="/assets/wallet.js?v=20260909b"></script>
<script src="/assets/wallet.js?v=20260909c"></script>
<script src="/assets/promo.js?v=20260909b"></script>
<script src="/assets/my.js?v=20260909f"></script>
<script src="/assets/my.js?v=20260909g"></script>
<script src="/assets/chat.js?v=20260907l"></script>
</body>
</html>
+12 -1
View File
@@ -872,11 +872,22 @@ const server = http.createServer(async (req, res) => {
const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const levels = await accounts.downline(s.email, 3);
// what each person has paid THIS member so far: sum of TierPaid events where
// this member is the recipient and that person is the buyer (all indexed events)
const myId = await auth.refreshMemberId(s);
const earnedBy = {};
if (myId) {
for (const ev of chain.recentEvents(1e9)) {
if (ev.type === 'TierPaid' && ev.recipientId === myId && ev.buyerId)
earnedBy[ev.buyerId] = (BigInt(earnedBy[ev.buyerId] || '0') + BigInt(ev.amountWei)).toString();
}
}
const out = levels.map(L => ({ level: L.level, members: L.members.map(m => ({
memberId: m.memberId || 0,
name: m.username ? '@' + m.username : m.memberId ? 'member #' + m.memberId : 'member',
email: L.level === 1 ? m.email : null, // directs only
joined: m.created })) }));
joined: m.created,
earnedWei: (m.memberId && earnedBy[m.memberId]) || '0' })) }));
return json(res, 200, { levels: out, counts: out.map(L => L.members.length) });
}
// -- broadcast a message to your downline (1/day), on-site inbox + email