diff --git a/README.md b/README.md index 4661dfa..c514320 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A human-facing catalog of small, runnable, frontend-only demonstrations built with live AirnodeHub operations. Each example is backed by a repository README and an agent-ready prompt. -This community workspace is open to everyone and maintained by API3. Its projects focus on the part a normal API example often omits: how an application discovers a source, evaluates it, verifies the signed response locally, and carries the evidence forward. +This community workspace is open to everyone and maintained by API3. It is not an official API3 product. Its projects focus on the part a normal API example often omits: how an application discovers a source, evaluates it, verifies the signed response locally, and carries the evidence forward. ## Architecture boundary @@ -54,4 +54,4 @@ Browse the complete [use-case index](use-cases/README.md), inspect the proven [b ## Status -Open to community contributions and maintained by API3. +Open to community contributions and maintained by API3. Not an official API3 product. diff --git a/site/public/llms.txt b/site/public/llms.txt index 1ee91cc..e0d7c9b 100644 --- a/site/public/llms.txt +++ b/site/public/llms.txt @@ -1,6 +1,6 @@ # AirnodeHub Community -> An open catalog of runnable, frontend-only AirnodeHub use cases, maintained by API3 and built for community contribution. +> An open catalog of runnable, frontend-only AirnodeHub use cases, maintained by API3 and built for community contribution. It is not an official API3 product. All demonstrations run in the browser without a proxy, database, account, or private API key. They use live AirnodeHub operations and expose the request, response, signer, verification result, and derived output. diff --git a/site/src/App.tsx b/site/src/App.tsx index de5967b..bcee5be 100644 --- a/site/src/App.tsx +++ b/site/src/App.tsx @@ -77,7 +77,7 @@ export default function App() { )} ); diff --git a/site/src/components/CandidateGrid.tsx b/site/src/components/CandidateGrid.tsx index 9e636f8..c4b009f 100644 --- a/site/src/components/CandidateGrid.tsx +++ b/site/src/components/CandidateGrid.tsx @@ -54,7 +54,7 @@ export function CandidateGrid({ {mode === 'catalog-fallback' ? 'Source search was unavailable, so the demo checked its built-in live source list.' : mode === 'resolver+catalog' - ? 'AirnodeHub matched the question; the demo ranked all three known ETH/USD sources.' + ? 'AirnodeHub matched the question. The demo also added its own known ETH/USD sources so the policy can rank them.' : mode === 'resolver' ? 'AirnodeHub matched the question to live sources.' : 'No source met the current settings.'} @@ -78,6 +78,9 @@ export function CandidateGrid({
{sourceLabel(decision.listing)} + {candidate.origin === 'demo-catalog' && ( + Added by the demo + )} {decisionLabel(decision.status)} diff --git a/site/src/components/ProofPanel.tsx b/site/src/components/ProofPanel.tsx index b593b3c..cfcfafc 100644 --- a/site/src/components/ProofPanel.tsx +++ b/site/src/components/ProofPanel.tsx @@ -63,7 +63,7 @@ export function ProofPanel({
{verification.valid ? 'ETH / USD' : 'Rejected value ยท ETH / USD'} - + $ {receipt.normalized.value.toLocaleString('en-US', { minimumFractionDigits: 2, diff --git a/site/src/lib/catalog.test.ts b/site/src/lib/catalog.test.ts index 19a93c7..cef994f 100644 --- a/site/src/lib/catalog.test.ts +++ b/site/src/lib/catalog.test.ts @@ -6,6 +6,7 @@ describe('price candidate discovery', () => { const resolvedCoinGecko = { ...PRICE_CATALOG[1], why: 'Returned by the live resolver.', + origin: 'resolver' as const, }; const candidates = supplementPriceCandidates([resolvedCoinGecko]); @@ -16,4 +17,15 @@ describe('price candidate discovery', () => { ]); expect(candidates[0]).toBe(resolvedCoinGecko); }); + + it('marks supplemented sources as demo additions rather than discoveries', () => { + const resolvedCoinGecko = { ...PRICE_CATALOG[1], origin: 'resolver' as const }; + const candidates = supplementPriceCandidates([resolvedCoinGecko]); + + expect(candidates.map((candidate) => candidate.origin)).toEqual([ + 'resolver', + 'demo-catalog', + 'demo-catalog', + ]); + }); }); diff --git a/site/src/lib/catalog.ts b/site/src/lib/catalog.ts index 1c0784e..2cb8c8e 100644 --- a/site/src/lib/catalog.ts +++ b/site/src/lib/catalog.ts @@ -19,6 +19,7 @@ export const PRICE_CATALOG: Candidate[] = [ example: { name: 'ETH/USD' }, returns: emptyReturns, payment: null, + origin: 'demo-catalog', why: 'First-party aggregated ETH/USD data from Nodary.', }, { @@ -36,6 +37,7 @@ export const PRICE_CATALOG: Candidate[] = [ example: { ids: 'ethereum', vs_currencies: 'usd' }, returns: emptyReturns, payment: null, + origin: 'demo-catalog', why: 'Current Ethereum price relayed from CoinGecko.', }, { @@ -51,6 +53,7 @@ export const PRICE_CATALOG: Candidate[] = [ example: { assetClass: 'crypto', symbol: 'ETHUSD' }, returns: emptyReturns, payment: null, + origin: 'demo-catalog', why: 'Latest ETH/USD trade relayed from TickerLayer.', }, ]; diff --git a/site/src/lib/client.ts b/site/src/lib/client.ts index 2888fba..1f6da2e 100644 --- a/site/src/lib/client.ts +++ b/site/src/lib/client.ts @@ -122,23 +122,29 @@ async function discover( ); } - const usesPriceCatalog = supportsCatalogFallback(intent); - const candidates = usesPriceCatalog - ? supplementPriceCandidates(resolution.candidates) - : resolution.candidates; + const resolved: Candidate[] = resolution.candidates.map((candidate) => ({ + ...candidate, + origin: 'resolver', + })); + const candidates = supportsCatalogFallback(intent) + ? supplementPriceCandidates(resolved) + : resolved; + const addedCount = candidates.length - resolved.length; emit( onTrace, 'discover', - `${candidates.length} candidates discovered`, - usesPriceCatalog - ? 'The resolver matched the intent; the demo added its known ETH/USD sources for trust-policy ranking.' + addedCount + ? `${resolved.length} discovered, ${addedCount} added by the demo` + : `${resolved.length} candidates discovered`, + addedCount + ? 'The resolver returned the discovered sources. The demo added its own known ETH/USD sources so the trust policy can rank them, and marks them separately.' : 'Parameters, provenance, signer, and payment requirements came from the resolver.', 'success', ); return { candidates, - mode: usesPriceCatalog ? 'resolver+catalog' : 'resolver', + mode: addedCount ? 'resolver+catalog' : 'resolver', attempts: attempt, }; } catch (error) { diff --git a/site/src/lib/types.ts b/site/src/lib/types.ts index 6d89f78..aaa5eb2 100644 --- a/site/src/lib/types.ts +++ b/site/src/lib/types.ts @@ -8,6 +8,8 @@ export interface PaymentRequirement { maxAmountRequired: string; } +export type CandidateOrigin = 'resolver' | 'demo-catalog'; + export interface Candidate { listing: string; airnode: string; @@ -19,11 +21,12 @@ export interface Candidate { returns: Record; payment: PaymentRequirement | null; why: string; + origin: CandidateOrigin; } export interface Resolution { intent: string; - candidates: Candidate[]; + candidates: Omit[]; } export interface Attestation { diff --git a/site/src/styles.css b/site/src/styles.css index 58f0ef7..af52d10 100644 --- a/site/src/styles.css +++ b/site/src/styles.css @@ -1399,6 +1399,15 @@ main { font-weight: 600; } +.source-origin { + padding: 2px 7px; + color: var(--secondary); + background: var(--violet-soft); + border-radius: 5px; + font-size: 11px; + font-weight: 550; +} + .source-status { padding: 3px 7px; color: var(--secondary); @@ -4653,6 +4662,10 @@ summary:focus-visible { --value-flash-color: rgba(199, 48, 48, 0.16); } +.value-flash--rejected { + --value-flash-color: rgba(199, 48, 48, 0.16); +} + @keyframes value-flash { 0% { background-color: var(--value-flash-color);