feat(reputation): surface 'payment proof' as its own tier, never as verified
The proof tag is self-reported and unverifiable client-side, so it must not read as proof of payment. useTradeReputation now exposes withPaymentProof (mutual trades carrying a proof), shown on TradeReputationCard as 'with payment proof (self-reported - not verified)' distinct from the verified count.
This commit is contained in:
parent
1a834f7b40
commit
a538bd7add
3 changed files with 26 additions and 3 deletions
|
|
@ -927,8 +927,11 @@ session with **no other memory of this project**. So:
|
||||||
plus `tradedCount` (mutual trades) + `tradedTraders` (distinct people), and
|
plus `tradedCount` (mutual trades) + `tradedTraders` (distinct people), and
|
||||||
keeps `tradedSatsClaimed` (the raw claim) so the card shows both. New query
|
keeps `tradedSatsClaimed` (the raw claim) so the card shows both. New query
|
||||||
fields ⇒ `PERSIST_BUSTER` → **v5**. **There are no trade attestations on the
|
fields ⇒ `PERSIST_BUSTER` → **v5**. **There are no trade attestations on the
|
||||||
relay yet**, so the honest values are 0 today. (Broader anti-sybil plan —
|
relay yet**, so the honest values are 0 today. Related: `useTradeReputation`
|
||||||
claimed-vs-verified tiers, ring detection, PoW — is a follow-up.)
|
now exposes **`withPaymentProof`** — mutually-confirmed trades that carry a
|
||||||
|
`proof` tag — surfaced on `TradeReputationCard` as *"with payment proof
|
||||||
|
(self-reported — not verified)"*, **never** as verification. (Remaining
|
||||||
|
anti-sybil: ring detection + PoW.)
|
||||||
- **Reverse geocoding hits Nominatim — disclosed (2026-10-01).**
|
- **Reverse geocoding hits Nominatim — disclosed (2026-10-01).**
|
||||||
`src/lib/geocodeRegions.ts` (`reverseGeocodeRegions` / `geocodeBigDataCloud` /
|
`src/lib/geocodeRegions.ts` (`reverseGeocodeRegions` / `geocodeBigDataCloud` /
|
||||||
`pushRegionTags`) is the **single source of truth**, used by **spot create, spot
|
`pushRegionTags`) is the **single source of truth**, used by **spot create, spot
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,12 @@ export function TradeReputationCard({ pubkey, className }: { pubkey: string; cla
|
||||||
? `${data.verified} mutually-confirmed ${data.verified === 1 ? 'trade' : 'trades'} with ${people} ${people === 1 ? 'person' : 'people'}`
|
? `${data.verified} mutually-confirmed ${data.verified === 1 ? 'trade' : 'trades'} with ${people} ${people === 1 ? 'person' : 'people'}`
|
||||||
: 'Awaiting mutual confirmation'}
|
: 'Awaiting mutual confirmation'}
|
||||||
</p>
|
</p>
|
||||||
|
{data.withPaymentProof > 0 && (
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
<span className="font-bold text-[#ccff00]">{data.withPaymentProof}</span> with a payment
|
||||||
|
proof attached <span className="text-gray-600">(self-reported — not verified)</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{singlePartnerWarning && (
|
{singlePartnerWarning && (
|
||||||
<p className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1.5 text-xs text-amber-300">
|
<p className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1.5 text-xs text-amber-300">
|
||||||
⚠ Every confirmation is with the same account. Mutual confirmation is
|
⚠ Every confirmation is with the same account. Mutual confirmation is
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,13 @@ export interface TradeReputation {
|
||||||
purchases: number;
|
purchases: number;
|
||||||
/** Deals where both sides attested the same listing. */
|
/** Deals where both sides attested the same listing. */
|
||||||
verified: number;
|
verified: number;
|
||||||
|
/**
|
||||||
|
* Mutually-confirmed trades that ALSO carry a `proof` tag (a payment claim).
|
||||||
|
* ⚠️ The `proof` is self-reported and the app does NOT verify it — it is a
|
||||||
|
* stronger *claim*, never evidence of payment. Show it as "with payment proof",
|
||||||
|
* not "verified".
|
||||||
|
*/
|
||||||
|
withPaymentProof: number;
|
||||||
/**
|
/**
|
||||||
* Distinct counterparties with at least one mutually-confirmed trade.
|
* Distinct counterparties with at least one mutually-confirmed trade.
|
||||||
*
|
*
|
||||||
|
|
@ -182,7 +189,7 @@ export function useTradeReputation(pubkey: string | undefined) {
|
||||||
enabled: !!pubkey,
|
enabled: !!pubkey,
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
queryFn: async (c): Promise<TradeReputation> => {
|
queryFn: async (c): Promise<TradeReputation> => {
|
||||||
const empty: TradeReputation = { sales: 0, purchases: 0, verified: 0, verifiedCounterparties: 0, verifiedWith: [], counterparties: [], attestations: [] };
|
const empty: TradeReputation = { sales: 0, purchases: 0, verified: 0, withPaymentProof: 0, verifiedCounterparties: 0, verifiedWith: [], counterparties: [], attestations: [] };
|
||||||
if (!pubkey) return empty;
|
if (!pubkey) return empty;
|
||||||
|
|
||||||
const signal = AbortSignal.any([c.signal, AbortSignal.timeout(12000)]);
|
const signal = AbortSignal.any([c.signal, AbortSignal.timeout(12000)]);
|
||||||
|
|
@ -214,6 +221,7 @@ export function useTradeReputation(pubkey: string | undefined) {
|
||||||
let sales = 0;
|
let sales = 0;
|
||||||
let purchases = 0;
|
let purchases = 0;
|
||||||
let verified = 0;
|
let verified = 0;
|
||||||
|
let withPaymentProof = 0;
|
||||||
|
|
||||||
for (const [key, attestations] of byListing) {
|
for (const [key, attestations] of byListing) {
|
||||||
const coord = parseListingCoord(key);
|
const coord = parseListingCoord(key);
|
||||||
|
|
@ -232,6 +240,11 @@ export function useTradeReputation(pubkey: string | undefined) {
|
||||||
for (const a of attestors) {
|
for (const a of attestors) {
|
||||||
if (a !== pubkey) verifiedWith.set(a, (verifiedWith.get(a) ?? 0) + 1);
|
if (a !== pubkey) verifiedWith.set(a, (verifiedWith.get(a) ?? 0) + 1);
|
||||||
}
|
}
|
||||||
|
// A `proof` tag is a payment CLAIM, not verification — count it on its
|
||||||
|
// own tier so it is never confused with a confirmed trade.
|
||||||
|
if (attestations.some((e) => e.tags.some(([t]) => t === 'proof'))) {
|
||||||
|
withPaymentProof++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record counterparties (everyone else who attested this listing).
|
// Record counterparties (everyone else who attested this listing).
|
||||||
|
|
@ -248,6 +261,7 @@ export function useTradeReputation(pubkey: string | undefined) {
|
||||||
sales,
|
sales,
|
||||||
purchases,
|
purchases,
|
||||||
verified,
|
verified,
|
||||||
|
withPaymentProof,
|
||||||
verifiedCounterparties: verifiedWith.size,
|
verifiedCounterparties: verifiedWith.size,
|
||||||
verifiedWith: [...verifiedWith.entries()].map(([pubkey, count]) => ({ pubkey, count })),
|
verifiedWith: [...verifiedWith.entries()].map(([pubkey, count]) => ({ pubkey, count })),
|
||||||
counterparties: [...counterparties],
|
counterparties: [...counterparties],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue