this repo has no description attested.network/

feature: initial site

+2435
+293
app-developers.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>App Developers — attested.network</title> 7 + <meta name="description" content="Guide for integrating attested payment verification into ATProtocol applications."> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> 11 + <link rel="stylesheet" href="styles.css"> 12 + <script type="module"> 13 + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; 14 + mermaid.initialize({ 15 + startOnLoad: true, 16 + theme: 'base', 17 + themeVariables: { 18 + primaryColor: '#f5f3ff', 19 + primaryTextColor: '#111827', 20 + primaryBorderColor: '#ddd6fe', 21 + secondaryColor: '#f8f9fb', 22 + tertiaryColor: '#ffffff', 23 + lineColor: '#8b5cf6', 24 + textColor: '#111827', 25 + mainBkg: '#f5f3ff', 26 + nodeBorder: '#ddd6fe', 27 + clusterBkg: '#faf9fc', 28 + clusterBorder: '#e5e7eb', 29 + fontFamily: 'Inter, -apple-system, system-ui, sans-serif', 30 + fontSize: '13px' 31 + } 32 + }); 33 + </script> 34 + </head> 35 + <body> 36 + 37 + <div class="draft-banner"> 38 + <p><strong>Draft &mdash; Not yet published.</strong> This specification is in active development and is not ready for review or critique. Stay tuned for formal announcements.</p> 39 + </div> 40 + 41 + <div class="topnav"> 42 + <div class="topnav-inner"> 43 + <a class="topnav-logo" href="/"><span>attested</span>.network</a> 44 + <nav> 45 + <ul class="topnav-links"> 46 + <li><a href="index.html">Spec</a></li> 47 + <li><a href="brokers.html">Brokers</a></li> 48 + <li><a href="app-developers.html" class="active">App Developers</a></li> 49 + <li><a href="recipients.html">Recipients</a></li> 50 + <li><a href="payers.html">Payers</a></li> 51 + <li><a href="scenarios.html">Scenarios</a></li> 52 + </ul> 53 + </nav> 54 + </div> 55 + </div> 56 + 57 + <div class="hero-sm"> 58 + <div class="hero-inner"> 59 + <div class="breadcrumb"><a href="index.html">Home</a> / App Developers</div> 60 + <h1>App <span>Developers</span></h1> 61 + <p class="subtitle">Integrate payment verification into your ATProtocol application.</p> 62 + </div> 63 + </div> 64 + 65 + <main class="container"> 66 + 67 + <!-- Why Verify Payments? --> 68 + <section id="why-verify"> 69 + <h2>Why Verify Payments?</h2> 70 + <p class="section-desc">Payment verification unlocks a range of features for ATProtocol applications, from supporter recognition to premium content delivery.</p> 71 + 72 + <div class="card-grid"> 73 + <div class="card"> 74 + <h4>Gate Content</h4> 75 + <p>Restrict access to posts, media, or feeds based on whether a viewer has an active payment relationship with the creator.</p> 76 + </div> 77 + <div class="card"> 78 + <h4>Supporter Badges</h4> 79 + <p>Display visual indicators next to users who financially support a creator, building community recognition and social proof.</p> 80 + </div> 81 + <div class="card"> 82 + <h4>Premium Features</h4> 83 + <p>Unlock advanced functionality for paying supporters, such as extended upload limits, priority replies, or custom themes.</p> 84 + </div> 85 + <div class="card"> 86 + <h4>Payment History</h4> 87 + <p>Show a verifiable timeline of payments between accounts, useful for transparency dashboards and financial reporting.</p> 88 + </div> 89 + <div class="card"> 90 + <h4>Sponsorship Proof</h4> 91 + <p>Prove sponsorship relationships between accounts on-protocol, enabling verified sponsor listings and partnership displays.</p> 92 + </div> 93 + <div class="card"> 94 + <h4>Access Control</h4> 95 + <p>Combine payment status with other signals to build nuanced access policies for communities, groups, or collaborative spaces.</p> 96 + </div> 97 + </div> 98 + </section> 99 + 100 + <!-- Checking Payment Status --> 101 + <section id="checking-status"> 102 + <h2>Checking Payment Status</h2> 103 + <p class="section-desc">The quickest way to check whether a payment exists between two accounts is to call <code>network.attested.payment.lookup</code> on a broker's XRPC endpoint.</p> 104 + 105 + <p>Supply the <code>payer</code> and <code>recipient</code> DIDs as query parameters. You can optionally filter by <code>paymentType</code> using the full NSID of the payment collection (e.g. <code>network.attested.payment.recurring</code>). You can also pass one or more <code>brokers</code> DIDs to ensure results have at least one validating signature from a broker you trust, or pass <code>entitlements</code> AT-URIs to filter for payments that grant specific entitlements.</p> 106 + 107 + <div class="code-block"> 108 + <div class="code-label">Request</div> 109 + <pre><span class="hl-comment">// GET request to broker's XRPC endpoint</span> 110 + GET /xrpc/network.attested.payment.lookup 111 + <span class="hl-punc">?</span><span class="hl-key">payer</span><span class="hl-punc">=</span><span class="hl-str">did:plc:abc123supporter</span> 112 + <span class="hl-punc">&amp;</span><span class="hl-key">recipient</span><span class="hl-punc">=</span><span class="hl-str">did:plc:xyz789creator</span> 113 + <span class="hl-punc">&amp;</span><span class="hl-key">paymentType</span><span class="hl-punc">=</span><span class="hl-str">network.attested.payment.recurring</span> 114 + <span class="hl-punc">&amp;</span><span class="hl-key">brokers</span><span class="hl-punc">=</span><span class="hl-str">did:plc:broker456</span></pre> 115 + </div> 116 + 117 + <div class="code-block"> 118 + <div class="code-label">Response</div> 119 + <pre><span class="hl-punc">{</span> 120 + <span class="hl-key">"payments"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 121 + <span class="hl-punc">{</span> 122 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.recurring"</span><span class="hl-punc">,</span> 123 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:abc123supporter/network.attested.payment.recurring/3abc"</span><span class="hl-punc">,</span> 124 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreie..."</span><span class="hl-punc">,</span> 125 + <span class="hl-key">"recipient"</span><span class="hl-punc">:</span> <span class="hl-str">"did:plc:xyz789creator"</span><span class="hl-punc">,</span> 126 + <span class="hl-key">"amount"</span><span class="hl-punc">:</span> <span class="hl-num">500</span><span class="hl-punc">,</span> 127 + <span class="hl-key">"currency"</span><span class="hl-punc">:</span> <span class="hl-str">"USD"</span><span class="hl-punc">,</span> 128 + <span class="hl-key">"interval"</span><span class="hl-punc">:</span> <span class="hl-str">"monthly"</span><span class="hl-punc">,</span> 129 + <span class="hl-key">"createdAt"</span><span class="hl-punc">:</span> <span class="hl-str">"2026-03-15T10:30:00Z"</span><span class="hl-punc">,</span> 130 + <span class="hl-key">"entitlements"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 131 + <span class="hl-punc">{</span> <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:xyz789creator/com.example.product/3jkl"</span><span class="hl-punc">,</span> <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreik..."</span> <span class="hl-punc">}</span> 132 + <span class="hl-punc">]</span><span class="hl-punc">,</span> 133 + <span class="hl-key">"signatures"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 134 + <span class="hl-punc">{</span> <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:xyz789creator/network.attested.payment.proof/3def"</span><span class="hl-punc">,</span> <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreig..."</span> <span class="hl-punc">}</span><span class="hl-punc">,</span> 135 + <span class="hl-punc">{</span> <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:broker456/network.attested.payment.proof/3ghi"</span><span class="hl-punc">,</span> <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreih..."</span> <span class="hl-punc">}</span> 136 + <span class="hl-punc">]</span> 137 + <span class="hl-punc">}</span> 138 + <span class="hl-punc">]</span> 139 + <span class="hl-punc">}</span></pre> 140 + </div> 141 + 142 + <div class="callout"> 143 + <p><strong>Broker discovery.</strong> It is up to the recipient to contextually hint or broadcast which payment servicers (brokers) they use. The recipient&rsquo;s list may include more than one broker DID&mdash;each with a <code>#AttestedNetwork</code> service endpoint. Your application should resolve these DIDs and help the payer select the best option at that time, whether by preference order, availability, supported payment methods, or other criteria.</p> 144 + </div> 145 + </section> 146 + 147 + <!-- Verification Walkthrough --> 148 + <section id="verification"> 149 + <h2>Verification Walkthrough</h2> 150 + <p class="section-desc">When you need to cryptographically verify a payment record rather than trusting a broker's lookup response, follow these five steps.</p> 151 + 152 + <ol class="steps"> 153 + <li> 154 + <strong>Fetch the payment record</strong> from the supporter's repository using <code>com.atproto.repo.getRecord</code>. The record lives under the <code>network.attested.payment</code> collection in the payer's repo. 155 + </li> 156 + <li> 157 + <strong>Strip the <code>signatures</code> array</strong> from the record. The signatures are not part of the content-addressed payload and must be removed before hashing. 158 + </li> 159 + <li> 160 + <strong>Prepare attestation metadata.</strong> Take each entry from the signatures array, add the repository DID as the signer identity, and strip the <code>cid</code> and <code>signature</code> fields. Insert the resulting object as the <code>$sig</code> field on the record. 161 + </li> 162 + <li> 163 + <strong>Serialize and hash.</strong> Encode the prepared record as DAG-CBOR, hash the bytes with SHA-256, and wrap the digest as a CIDv1 with the DAG-CBOR codec (<code>0x71</code>). 164 + </li> 165 + <li> 166 + <strong>Fetch and compare proof records.</strong> Using the <code>strongRef</code> URIs from the signatures array, retrieve the corresponding proof records from the creator's and broker's repositories. Confirm that the CID you computed matches the CID referenced in each proof record. 167 + </li> 168 + </ol> 169 + 170 + <div class="callout"> 171 + <p><strong>Why verify locally?</strong> Broker lookup is convenient but requires trusting the broker's response. Local verification lets your app independently confirm that the payment record is authentic and has been attested by the expected parties, without relying on any single intermediary.</p> 172 + </div> 173 + </section> 174 + 175 + <!-- Trust Models --> 176 + <section id="trust-models"> 177 + <h2>Trust Models</h2> 178 + <p class="section-desc">Your app can adopt different trust strategies depending on security requirements and the nature of the payment-gated feature.</p> 179 + 180 + <ul class="trust-list"> 181 + <li> 182 + <strong>Strict</strong> 183 + <span>Require attestation proofs from both the creator (recipient) and a specific trusted broker. This is the most secure model and is recommended for high-value access control, financial dashboards, or any context where payment fraud would have significant consequences.</span> 184 + </li> 185 + <li> 186 + <strong>Creator-Trusted</strong> 187 + <span>Accept any payment record that the creator has attested, regardless of which broker facilitated it. Suitable for social features like supporter badges or shout-outs, where the creator's endorsement is the primary signal.</span> 188 + </li> 189 + <li> 190 + <strong>Federated</strong> 191 + <span>Maintain a set of trusted broker DIDs and accept attestations from any of them. This balances security with flexibility, allowing your app to work with multiple brokers while still filtering out unknown or untrusted attestors. Good for platforms that aggregate content from many creators.</span> 192 + </li> 193 + </ul> 194 + </section> 195 + 196 + <!-- Initiating Payments --> 197 + <section id="initiating-payments"> 198 + <h2>Initiating Payments</h2> 199 + <p class="section-desc">Your app can trigger a payment flow on behalf of the user, guiding them through the process without leaving your interface until checkout.</p> 200 + 201 + <ol class="steps"> 202 + <li> 203 + <strong>Resolve the recipient's DID document</strong> to discover their available payment servicers. Use the identity resolution layer of ATProtocol to fetch the DID doc. 204 + </li> 205 + <li> 206 + <strong>Find <code>#AttestedNetwork</code> service endpoints</strong> in the DID document. Each entry represents a broker or servicer that handles payments for this recipient. 207 + </li> 208 + <li> 209 + <strong>Present available servicers to the user.</strong> If multiple endpoints exist, let the user choose which payment provider they prefer. 210 + </li> 211 + <li> 212 + <strong>Call <code>network.attested.payment.initiate</code></strong> on the chosen servicer's XRPC endpoint, passing the product identifier and the payer's DID. 213 + </li> 214 + <li> 215 + <strong>Redirect the payer to the returned URL.</strong> The servicer responds with a checkout URL and a polling token. Open the URL in the user's browser to complete the payment. 216 + </li> 217 + <li> 218 + <strong>Poll <code>network.attested.payment.status</code></strong> with the token to track the payment outcome. On success, the response includes a <code>strongRef</code> pointing to the newly created payment record. 219 + </li> 220 + </ol> 221 + 222 + <div class="mermaid-wrapper"> 223 + <pre class="mermaid"> 224 + sequenceDiagram 225 + participant App 226 + participant DIDDoc as Recipient DID Doc 227 + participant Servicer as Payment Servicer 228 + participant Browser as Payer Browser 229 + 230 + App->>DIDDoc: Resolve recipient DID 231 + DIDDoc-->>App: Service endpoints (#AttestedNetwork) 232 + App->>Servicer: network.attested.payment.initiate 233 + Servicer-->>App: checkoutUrl + pollingToken 234 + App->>Browser: Redirect to checkoutUrl 235 + Browser->>Servicer: Complete payment 236 + Servicer-->>Browser: Payment confirmation 237 + loop Poll for result 238 + App->>Servicer: network.attested.payment.status(token) 239 + end 240 + Servicer-->>App: strongRef (success) or error (failed) 241 + </pre> 242 + </div> 243 + 244 + <div class="code-block"> 245 + <div class="code-label">Initiate Request</div> 246 + <pre><span class="hl-punc">{</span> 247 + <span class="hl-key">"recipient"</span><span class="hl-punc">:</span> <span class="hl-str">"did:plc:xyz789creator"</span><span class="hl-punc">,</span> 248 + <span class="hl-key">"payer"</span><span class="hl-punc">:</span> <span class="hl-str">"did:plc:abc123supporter"</span><span class="hl-punc">,</span> 249 + <span class="hl-key">"product"</span><span class="hl-punc">:</span> <span class="hl-str">"monthly-subscription"</span> 250 + <span class="hl-punc">}</span></pre> 251 + </div> 252 + 253 + <div class="code-block"> 254 + <div class="code-label">Initiate Response</div> 255 + <pre><span class="hl-punc">{</span> 256 + <span class="hl-key">"checkoutUrl"</span><span class="hl-punc">:</span> <span class="hl-str">"https://broker.example.com/pay/sess_abc123"</span><span class="hl-punc">,</span> 257 + <span class="hl-key">"token"</span><span class="hl-punc">:</span> <span class="hl-str">"poll_tok_abc123"</span> 258 + <span class="hl-punc">}</span></pre> 259 + </div> 260 + 261 + <div class="code-block"> 262 + <div class="code-label">Status Response (Success)</div> 263 + <pre><span class="hl-punc">{</span> 264 + <span class="hl-key">"status"</span><span class="hl-punc">:</span> <span class="hl-str">"completed"</span><span class="hl-punc">,</span> 265 + <span class="hl-key">"paymentRef"</span><span class="hl-punc">:</span> <span class="hl-punc">{</span> 266 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:abc123supporter/network.attested.payment/3abc"</span><span class="hl-punc">,</span> 267 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreie..."</span> 268 + <span class="hl-punc">}</span> 269 + <span class="hl-punc">}</span></pre> 270 + </div> 271 + </section> 272 + 273 + <!-- Private Payments --> 274 + <section id="private-payments"> 275 + <h2>Private Payments</h2> 276 + <p class="section-desc">Payment records stored in Permissioned Data Spaces follow the same verification mechanics described above.</p> 277 + 278 + <p>The only difference is access: records in a permissioned space are not publicly readable. Your application must hold valid space credentials to fetch the payment record and its associated proof records. Once retrieved, the verification steps (stripping signatures, computing the CID, and comparing against proof records) are identical to public payments.</p> 279 + 280 + <div class="callout"> 281 + <p><strong>Credential requirements.</strong> Contact the space operator to obtain read credentials. Your app will need to present these credentials when calling <code>com.atproto.repo.getRecord</code> for records stored in the permissioned space. The broker lookup endpoint may also require credentials if the broker enforces access control on private payment data.</p> 282 + </div> 283 + </section> 284 + 285 + </main> 286 + 287 + <footer> 288 + <div class="container"> 289 + <p>attested.network &middot; Built on <a href="https://badge.blue">badge.blue</a> attestations &middot; <a href="https://atproto.com">ATProtocol</a></p> 290 + </div> 291 + </footer> 292 + </body> 293 + </html>
+344
brokers.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Payment Brokers — attested.network</title> 7 + <meta name="description" content="Guide for implementing a payment broker (servicer) in the attested.network specification."> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> 11 + <link rel="stylesheet" href="styles.css"> 12 + <script type="module"> 13 + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; 14 + mermaid.initialize({ 15 + startOnLoad: true, 16 + theme: 'base', 17 + themeVariables: { 18 + primaryColor: '#f5f3ff', 19 + primaryTextColor: '#111827', 20 + primaryBorderColor: '#ddd6fe', 21 + secondaryColor: '#f8f9fb', 22 + tertiaryColor: '#ffffff', 23 + lineColor: '#8b5cf6', 24 + textColor: '#111827', 25 + mainBkg: '#f5f3ff', 26 + nodeBorder: '#ddd6fe', 27 + clusterBkg: '#faf9fc', 28 + clusterBorder: '#e5e7eb', 29 + fontFamily: 'Inter, -apple-system, system-ui, sans-serif', 30 + fontSize: '13px' 31 + } 32 + }); 33 + </script> 34 + </head> 35 + <body> 36 + 37 + <div class="draft-banner"> 38 + <p><strong>Draft &mdash; Not yet published.</strong> This specification is in active development and is not ready for review or critique. Stay tuned for formal announcements.</p> 39 + </div> 40 + 41 + <div class="topnav"> 42 + <div class="topnav-inner"> 43 + <a class="topnav-logo" href="/"><span>attested</span>.network</a> 44 + <nav> 45 + <ul class="topnav-links"> 46 + <li><a href="index.html">Spec</a></li> 47 + <li><a href="brokers.html" class="active">Brokers</a></li> 48 + <li><a href="app-developers.html">App Developers</a></li> 49 + <li><a href="recipients.html">Recipients</a></li> 50 + <li><a href="payers.html">Payers</a></li> 51 + <li><a href="scenarios.html">Scenarios</a></li> 52 + </ul> 53 + </nav> 54 + </div> 55 + </div> 56 + 57 + <div class="hero-sm"> 58 + <div class="hero-inner"> 59 + <div class="breadcrumb"><a href="index.html">Home</a> / Payment Brokers</div> 60 + <h1>Payment <span>Brokers</span></h1> 61 + <p class="subtitle">How to implement a payment servicer that processes transactions and writes attestation proofs.</p> 62 + </div> 63 + </div> 64 + 65 + <main class="container"> 66 + 67 + <!-- What is a Broker? --> 68 + <section> 69 + <h2>What is a Broker?</h2> 70 + <p class="section-desc">A broker (or payment servicer) facilitates payment between payers and recipients on ATProtocol and acts as a witness to the exchange.</p> 71 + 72 + <p>A broker is whatever facilitates the exchange of value. One broker might process Stripe transactions. Another might handle peer-to-peer cash. Another might simply witness a virtual high-five. The role is to <strong>facilitate and witness</strong> the exchange between payer and recipient, then write <code>network.attested.payment.proof</code> records and expose XRPC endpoints for payment initiation and verification.</p> 73 + 74 + <p>Recipients publish an ordered list of broker DIDs with <code>#AttestedNetwork</code> service endpoints in their DID document. When a payer wants to pay a recipient, the app resolves the recipient's DID document, finds the broker endpoints, and initiates the payment flow through one of them.</p> 75 + 76 + <div class="callout"> 77 + <p><strong>Key principle:</strong> Brokers are untrusted intermediaries. The attestation proof they write is independently verifiable by anyone &mdash; the CID-based proof chain means you can confirm a payment without trusting the broker's word.</p> 78 + </div> 79 + 80 + <h3>Responsibilities</h3> 81 + <ul class="steps"> 82 + <li>Accept payment initiation requests via XRPC from apps acting on behalf of payers</li> 83 + <li>Facilitate the actual exchange (Stripe transaction, peer-to-peer cash, or any other mechanism)</li> 84 + <li>Write payment records to the payer's repository via inter-service auth</li> 85 + <li>Compute attestation CIDs and write proof records to the broker's own repository</li> 86 + <li>Expose status and lookup endpoints so apps can verify payment state</li> 87 + </ul> 88 + </section> 89 + 90 + <!-- DID Document Setup --> 91 + <section> 92 + <h2>DID Document Setup</h2> 93 + <p class="section-desc">Brokers must configure an <code>#AttestedNetwork</code> service endpoint in their DID document so recipients can discover and reference them.</p> 94 + 95 + <p>When a recipient adds your broker to their supported servicers list, they reference your DID. Apps then resolve your DID document, find the <code>#AttestedNetwork</code> service entry, and use its <code>serviceEndpoint</code> as the base URL for all XRPC calls.</p> 96 + 97 + <p>Add the following service entry to your DID document:</p> 98 + 99 + <div class="code-block"> 100 + <div class="code-label">DID Document Service Entry</div> 101 + <pre><span class="hl-punc">{</span> 102 + <span class="hl-key">"id"</span><span class="hl-punc">:</span> <span class="hl-str">"#AttestedNetwork"</span><span class="hl-punc">,</span> 103 + <span class="hl-key">"type"</span><span class="hl-punc">:</span> <span class="hl-str">"AttestedNetworkServicer"</span><span class="hl-punc">,</span> 104 + <span class="hl-key">"serviceEndpoint"</span><span class="hl-punc">:</span> <span class="hl-str">"https://pay.example.com"</span> 105 + <span class="hl-punc">}</span></pre> 106 + </div> 107 + 108 + <div class="callout"> 109 + <p><strong>Note:</strong> The <code>serviceEndpoint</code> URL must be HTTPS and should point to the root of your XRPC service. All three payment methods (<code>initiate</code>, <code>status</code>, <code>lookup</code>) will be called against this base URL.</p> 110 + </div> 111 + </section> 112 + 113 + <!-- XRPC Methods to Implement --> 114 + <section> 115 + <h2>XRPC Methods to Implement</h2> 116 + <p class="section-desc">Brokers must implement three XRPC methods that apps use to initiate payments, check status, and look up verified payment records.</p> 117 + 118 + <!-- initiate --> 119 + <div class="lexicon-block"> 120 + <div class="lexicon-header"> 121 + <div class="lexicon-tag">Procedure &middot; POST</div> 122 + <div class="lexicon-name">network.attested.payment.initiate</div> 123 + <p class="lexicon-desc">Initiate a new payment flow. Called by an app on behalf of a payer using inter-service auth. Returns an opaque token and a URL where the payer completes the payment in a browser.</p> 124 + </div> 125 + <div class="lexicon-body"> 126 + <p class="sub-heading">Input</p> 127 + <table class="schema-table"> 128 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 129 + <tbody> 130 + <tr> 131 + <td><span class="field-name">product</span></td> 132 + <td><span class="field-type">string</span></td> 133 + <td><span class="field-desc">Identifier for the product or subscription being purchased. Defined by the recipient.</span></td> 134 + </tr> 135 + </tbody> 136 + </table> 137 + <p class="sub-heading">Output</p> 138 + <table class="schema-table"> 139 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 140 + <tbody> 141 + <tr> 142 + <td><span class="field-name">token</span></td> 143 + <td><span class="field-type">string</span></td> 144 + <td><span class="field-desc">Opaque token used to track this payment session. Pass to <code>status</code> to poll for completion.</span></td> 145 + </tr> 146 + <tr> 147 + <td><span class="field-name">url</span></td> 148 + <td><span class="field-type">string</span></td> 149 + <td><span class="field-desc">URL where the payer completes the payment flow in a browser (e.g. a Stripe Checkout page).</span></td> 150 + </tr> 151 + </tbody> 152 + </table> 153 + </div> 154 + </div> 155 + 156 + <!-- status --> 157 + <div class="lexicon-block"> 158 + <div class="lexicon-header"> 159 + <div class="lexicon-tag">Query &middot; GET</div> 160 + <div class="lexicon-name">network.attested.payment.status</div> 161 + <p class="lexicon-desc">Check the status of a payment initiated with a token from <code>initiate</code>. When completed, includes a strong reference to the proof record.</p> 162 + </div> 163 + <div class="lexicon-body"> 164 + <p class="sub-heading">Parameters</p> 165 + <table class="schema-table"> 166 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 167 + <tbody> 168 + <tr> 169 + <td><span class="field-name">token</span></td> 170 + <td><span class="field-type">string</span></td> 171 + <td><span class="field-desc">The opaque token returned from <code>initiate</code>.</span></td> 172 + </tr> 173 + </tbody> 174 + </table> 175 + <p class="sub-heading">Output</p> 176 + <table class="schema-table"> 177 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 178 + <tbody> 179 + <tr> 180 + <td><span class="field-name">status</span></td> 181 + <td><span class="field-type">string</span></td> 182 + <td><span class="field-desc">One of <code>"pending"</code>, <code>"completed"</code>, or <code>"failed"</code>.</span></td> 183 + </tr> 184 + <tr> 185 + <td><span class="field-name">ref</span></td> 186 + <td><span class="field-type">com.atproto.repo.strongRef</span> <span class="constraint">optional</span></td> 187 + <td><span class="field-desc">When status is <code>"completed"</code>, a strong reference (URI + CID) to the <code>network.attested.payment.proof</code> record in the broker's repository.</span></td> 188 + </tr> 189 + </tbody> 190 + </table> 191 + </div> 192 + </div> 193 + 194 + <!-- lookup --> 195 + <div class="lexicon-block"> 196 + <div class="lexicon-header"> 197 + <div class="lexicon-tag">Query &middot; GET</div> 198 + <div class="lexicon-name">network.attested.payment.lookup</div> 199 + <p class="lexicon-desc">Look up verified payment records between a payer and recipient. Used by apps to check whether a payer has an active payment for a given recipient.</p> 200 + </div> 201 + <div class="lexicon-body"> 202 + <p class="sub-heading">Parameters</p> 203 + <table class="schema-table"> 204 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 205 + <tbody> 206 + <tr> 207 + <td><span class="field-name">payer</span></td> 208 + <td><span class="field-type">did</span></td> 209 + <td><span class="field-desc">DID of the payer.</span></td> 210 + </tr> 211 + <tr> 212 + <td><span class="field-name">recipient</span></td> 213 + <td><span class="field-type">did</span></td> 214 + <td><span class="field-desc">DID of the recipient.</span></td> 215 + </tr> 216 + <tr> 217 + <td><span class="field-name">paymentType</span></td> 218 + <td><span class="field-type">string</span> <span class="constraint">optional</span></td> 219 + <td><span class="field-desc">Filter by payment collection. Must be a full NSID: <code>network.attested.payment.oneTime</code>, <code>network.attested.payment.recurring</code>, or <code>network.attested.payment.scheduled</code>.</span></td> 220 + </tr> 221 + <tr> 222 + <td><span class="field-name">brokers</span></td> 223 + <td><span class="field-type">array(did)</span> <span class="constraint">optional, repeating</span></td> 224 + <td><span class="field-desc">List of broker DIDs. When provided, only returns payments that have at least one validating signature from an identity in this list.</span></td> 225 + </tr> 226 + <tr> 227 + <td><span class="field-name">entitlements</span></td> 228 + <td><span class="field-type">array(at-uri)</span> <span class="constraint">optional, repeating</span></td> 229 + <td><span class="field-desc">List of entitlement AT-URIs. When provided, only returns payments whose <code>entitlements</code> array contains one or more of the given references.</span></td> 230 + </tr> 231 + </tbody> 232 + </table> 233 + <p class="sub-heading">Output</p> 234 + <table class="schema-table"> 235 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 236 + <tbody> 237 + <tr> 238 + <td><span class="field-name">payments</span></td> 239 + <td><span class="field-type">array(union)</span></td> 240 + <td><span class="field-desc">Array of verified payment records matching the query. Each element is a union of <code>network.attested.payment.oneTime</code>, <code>network.attested.payment.recurring</code>, or <code>network.attested.payment.scheduled</code>.</span></td> 241 + </tr> 242 + </tbody> 243 + </table> 244 + </div> 245 + </div> 246 + 247 + <div class="callout"> 248 + <p><strong>Auth model:</strong> The <code>initiate</code> method uses ATProtocol inter-service auth &mdash; the calling app presents a signed service token on behalf of the payer. The <code>status</code> and <code>lookup</code> methods can be called with standard auth or unauthenticated, depending on your privacy requirements.</p> 249 + </div> 250 + </section> 251 + 252 + <!-- Writing Attestation Proofs --> 253 + <section> 254 + <h2>Writing Attestation Proofs</h2> 255 + <p class="section-desc">When a payment completes, the broker writes the payment record and a corresponding attestation proof. This is the core of the system.</p> 256 + 257 + <h3>Steps</h3> 258 + <ol class="steps"> 259 + <li>Write the payment record (<code>oneTime</code>, <code>recurring</code>, or <code>scheduled</code>) to the <strong>payer's repository</strong> using inter-service auth.</li> 260 + <li>Compute the attestation CID per the badge.blue spec: strip signatures from the record, add a <code>$sig</code> field containing the payer's repository DID, DAG-CBOR serialize, SHA-256 hash, produce a CIDv1.</li> 261 + <li>Write a <code>network.attested.payment.proof</code> record to the <strong>broker's own repository</strong> containing the CID and an optional <code>status</code>.</li> 262 + <li>Update the payment record's <code>signatures</code> array in the payer's repo with a <code>com.atproto.repo.strongRef</code> pointing to the proof record.</li> 263 + </ol> 264 + 265 + <div class="code-block"> 266 + <div class="code-label">Proof Record Example</div> 267 + <pre><span class="hl-punc">{</span> 268 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.proof"</span><span class="hl-punc">,</span> 269 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreig5..."</span> 270 + <span class="hl-punc">}</span></pre> 271 + </div> 272 + 273 + <h3>Payment Flow</h3> 274 + <p>The following diagram shows the complete flow from payment completion through proof writing:</p> 275 + 276 + <div class="mermaid-wrapper"> 277 + <pre class="mermaid"> 278 + sequenceDiagram 279 + participant PP as Payment Processor 280 + participant B as Broker 281 + participant PR as Payer's Repo 282 + participant BR as Broker's Repo 283 + 284 + PP->>B: Payment webhook (success) 285 + B->>PR: Write payment record 286 + PR-->>B: Record AT-URI + CID 287 + B->>B: Compute attestation CID 288 + B->>BR: Write proof record 289 + BR-->>B: Proof AT-URI + CID 290 + B->>PR: Update signatures array with strongRef 291 + PR-->>B: Confirmed 292 + </pre> 293 + </div> 294 + </section> 295 + 296 + <!-- Private Payments --> 297 + <section> 298 + <h2>Private Payments</h2> 299 + <p class="section-desc">For payments that should not be publicly visible, brokers manage Permissioned Data Spaces on behalf of the payer.</p> 300 + 301 + <p>When a payment is marked as private, the broker additionally creates a permissioned space in the payer's repository and ensures that only the relevant parties &mdash; the payer, the recipient, and the broker itself &mdash; have read access to the payment record.</p> 302 + 303 + <p>The attestation mechanics are identical to public payments. The proof record is still written to the broker's repository and the CID chain is still independently verifiable. The only difference is that the underlying payment record lives inside a permissioned space rather than being publicly readable.</p> 304 + 305 + <div class="callout"> 306 + <p><strong>Implementation note:</strong> The broker must have write access to the payer's permissioned data space collection. This is granted via the same inter-service auth mechanism used for writing payment records. Ensure your access control logic correctly scopes permissions to only the records your broker manages.</p> 307 + </div> 308 + </section> 309 + 310 + <!-- Invalidation --> 311 + <section> 312 + <h2>Invalidation</h2> 313 + <p class="section-desc">When support ends &mdash; whether through cancellation, failed retries, or refund &mdash; the broker should invalidate the attestation proof.</p> 314 + 315 + <div class="callout"> 316 + <p><strong>Guidance, not requirements.</strong> The following invalidation and retry processes are suggested patterns, not enforced rules. Brokers are free to implement whatever process they believe to be appropriate for their use case and their users.</p> 317 + </div> 318 + 319 + <p>To invalidate a payment, the broker deletes or updates its <code>network.attested.payment.proof</code> record and notifies the recipient. Apps that verify payments will see the proof is missing or marked invalid and treat the payment as no longer active.</p> 320 + 321 + <h3>Recurring Payment Retries</h3> 322 + <p>For recurring payments, a failed charge does not immediately invalidate the attestation. The broker should follow this retry schedule:</p> 323 + 324 + <ul class="steps"> 325 + <li><strong>Day 1:</strong> First retry attempt after the initial charge failure.</li> 326 + <li><strong>Day 3:</strong> Second retry attempt.</li> 327 + <li><strong>Day 7:</strong> Third and final retry attempt.</li> 328 + <li><strong>Day 8 (grace period expires):</strong> If all retries have failed, the broker deletes the proof record and notifies the recipient that the payment is no longer active.</li> 329 + </ul> 330 + 331 + <div class="callout"> 332 + <p><strong>Grace period:</strong> During the 8-day retry window, the existing proof record remains valid. The payer's access should not be interrupted while retries are in progress. Only after the grace period expires and all retries have failed should the broker invalidate the proof.</p> 333 + </div> 334 + </section> 335 + 336 + </main> 337 + 338 + <footer> 339 + <div class="container"> 340 + <p>attested.network &middot; Built on <a href="https://badge.blue">badge.blue</a> attestations &middot; <a href="https://atproto.com">ATProtocol</a></p> 341 + </div> 342 + </footer> 343 + </body> 344 + </html>
+567
index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>attested.network — Proof of Payment for ATProtocol</title> 7 + <meta name="description" content="An open specification for decentralized, cryptographically verifiable proof of payments."> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> 11 + <link rel="stylesheet" href="styles.css"> 12 + <script type="module"> 13 + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; 14 + mermaid.initialize({ 15 + startOnLoad: true, 16 + theme: 'base', 17 + themeVariables: { 18 + primaryColor: '#f5f3ff', 19 + primaryTextColor: '#111827', 20 + primaryBorderColor: '#ddd6fe', 21 + secondaryColor: '#f8f9fb', 22 + tertiaryColor: '#ffffff', 23 + lineColor: '#8b5cf6', 24 + textColor: '#111827', 25 + mainBkg: '#f5f3ff', 26 + nodeBorder: '#ddd6fe', 27 + clusterBkg: '#faf9fc', 28 + clusterBorder: '#e5e7eb', 29 + fontFamily: 'Inter, -apple-system, system-ui, sans-serif', 30 + fontSize: '13px' 31 + } 32 + }); 33 + </script> 34 + </head> 35 + <body> 36 + 37 + <div class="draft-banner"> 38 + <p><strong>Draft &mdash; Not yet published.</strong> This specification is in active development and is not ready for review or critique. Stay tuned for formal announcements.</p> 39 + </div> 40 + 41 + <!-- Top navigation --> 42 + <div class="topnav"> 43 + <div class="topnav-inner"> 44 + <a class="topnav-logo" href="/"><span>attested</span>.network</a> 45 + <nav> 46 + <ul class="topnav-links"> 47 + <li><a href="index.html" class="active">Spec</a></li> 48 + <li><a href="brokers.html">Brokers</a></li> 49 + <li><a href="app-developers.html">App Developers</a></li> 50 + <li><a href="recipients.html">Recipients</a></li> 51 + <li><a href="payers.html">Payers</a></li> 52 + <li><a href="scenarios.html">Scenarios</a></li> 53 + </ul> 54 + </nav> 55 + </div> 56 + </div> 57 + 58 + <!-- Hero --> 59 + <div class="hero"> 60 + <h1>Proof of Payment<br>for <span>ATProtocol</span></h1> 61 + <p class="subtitle">An open specification for decentralized, cryptographically verifiable proof of payments.</p> 62 + <div class="hero-badges"> 63 + <a class="hero-badge" href="https://badge.blue"> 64 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> 65 + Built on badge.blue 66 + </a> 67 + <a class="hero-badge" href="https://atproto.com"> 68 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> 69 + ATProtocol 70 + </a> 71 + </div> 72 + </div> 73 + 74 + <main class="container"> 75 + 76 + <!-- Guides --> 77 + <section id="guides" style="padding-bottom: 0;"> 78 + <h2>Documentation</h2> 79 + <p class="section-desc">Guides for every role in the attested payments ecosystem.</p> 80 + <div class="guide-grid"> 81 + <a class="guide-card" href="brokers.html"> 82 + <h4>Payment Brokers</h4> 83 + <p>Implement a payment servicer that processes transactions and writes attestation proofs.</p> 84 + <span class="guide-arrow">Read guide &rarr;</span> 85 + </a> 86 + <a class="guide-card" href="app-developers.html"> 87 + <h4>App Developers</h4> 88 + <p>Integrate payment verification into your ATProtocol application.</p> 89 + <span class="guide-arrow">Read guide &rarr;</span> 90 + </a> 91 + <a class="guide-card" href="recipients.html"> 92 + <h4>Recipients</h4> 93 + <p>Set up your identity to accept attested payments from supporters.</p> 94 + <span class="guide-arrow">Read guide &rarr;</span> 95 + </a> 96 + <a class="guide-card" href="payers.html"> 97 + <h4>Payers</h4> 98 + <p>Understand how your payments are recorded, verified, and portable.</p> 99 + <span class="guide-arrow">Read guide &rarr;</span> 100 + </a> 101 + <a class="guide-card" href="scenarios.html"> 102 + <h4>Scenarios</h4> 103 + <p>Real-world examples showing attested payments in action.</p> 104 + <span class="guide-arrow">Browse scenarios &rarr;</span> 105 + </a> 106 + </div> 107 + </section> 108 + 109 + <!-- Overview --> 110 + <section id="overview"> 111 + <h2>Overview</h2> 112 + <p class="section-desc">Payment proofs replace centralized payment databases with cryptographic attestation records distributed across ATProtocol repositories. Any application can independently verify a payment&mdash;no single platform controls the relationship.</p> 113 + 114 + <h3>Three-party attestation</h3> 115 + <p>Every payment creates records across three independent repositories. The supporter declares intent, the creator confirms receipt, and a broker&mdash;which facilitates and witnesses the exchange&mdash;writes its own proof. Each record is content-addressed and bound to its repository, preventing replay attacks.</p> 116 + 117 + <div class="mermaid-wrapper"> 118 + <pre class="mermaid"> 119 + graph LR 120 + subgraph SR["Supporter's Repo"] 121 + PAY["Payment Record<br/><i>oneTime | recurring | scheduled</i>"] 122 + SIG["signatures: [strongRef, ...]"] 123 + end 124 + 125 + subgraph CR["Creator's Repo"] 126 + CP["payment.proof<br/><i>Attests via CID</i>"] 127 + end 128 + 129 + subgraph BR["Broker's Repo"] 130 + BP["payment.proof<br/><i>Attests via CID</i>"] 131 + end 132 + 133 + SIG -- "strongRef" --> CP 134 + SIG -- "strongRef" --> BP 135 + </pre> 136 + </div> 137 + 138 + <div class="card-grid"> 139 + <div class="card"> 140 + <h4>Data Ownership</h4> 141 + <p>Each party controls their own records in their own repository. No single entity holds the full picture.</p> 142 + </div> 143 + <div class="card"> 144 + <h4>Cryptographic Integrity</h4> 145 + <p>CID-based content addressing ensures records cannot be modified after attestation. Change the data, break the hash.</p> 146 + </div> 147 + <div class="card"> 148 + <h4>Portable Relationships</h4> 149 + <p>Support relationships survive platform changes. Records live on ATProtocol&rsquo;s network, not in proprietary databases.</p> 150 + </div> 151 + <div class="card"> 152 + <h4>Open Broker Model</h4> 153 + <p>Any entity can serve as a broker because brokers facilitate payment. A broker might process Stripe transactions, handle peer-to-peer cash, or simply witness a virtual high-five. The role is to facilitate and witness the exchange between payer and recipient.</p> 154 + </div> 155 + </div> 156 + 157 + <div class="callout" style="margin-top: 32px;"> 158 + <p>This spec builds on <a href="https://badge.blue">badge.blue</a>&rsquo;s CID-first attestation framework. Every attestation CID is computed from the record content, metadata, and the repository DID&mdash;meaning a record copied to a different repository automatically invalidates all its attestations.</p> 159 + </div> 160 + 161 + <div class="callout" style="margin-top: 16px;"> 162 + <p><strong>Public &amp; private proofs.</strong> Payment records and proofs can live in a user&rsquo;s public repository for open verification, or within a <a href="https://dholms.leaflet.pub/3mhj6bcqats2o">Permissioned Data Space</a> for private access. The implementation is effectively identical in both cases&mdash;the same attestation mechanics, CID binding, and strongRef signatures apply. The only difference is that the payment servicer additionally manages creation of the permissioned space in the payer&rsquo;s repository and ensures all parties (creator, broker) have appropriate read access and permissions.</p> 163 + </div> 164 + 165 + <h3 style="margin-top: 32px;">Payment Intent</h3> 166 + <p>When a recipient wants to accept a payment, a structured discovery and initiation flow connects payer, recipient, and payment servicer:</p> 167 + 168 + <ol class="steps"> 169 + <li>The <strong>recipient</strong> signals which payment servicers they use by publishing an ordered list of DIDs that have a <code>#AttestedNetwork</code> service endpoint in their DID document</li> 170 + <li>The <strong>payer&rsquo;s client</strong> resolves these DIDs and presents the available servicers. The payer selects which one to use</li> 171 + <li>The payer&rsquo;s client makes an <strong>authenticated request</strong> (using inter-service authentication) to <code>network.attested.payment.initiate</code> on the selected servicer, passing the product identifier. The response includes a <strong>token</strong> and a <strong>URL</strong></li> 172 + <li>The payer is <strong>directed through a browser</strong> to the URL to complete the payment process (entering payment details, confirming terms, etc.)</li> 173 + <li>The payer&rsquo;s client polls <code>network.attested.payment.status</code> with the token. The response is either a <strong>strongRef</strong> pointing to the completed payment record, or a <strong>failed</strong> status</li> 174 + </ol> 175 + 176 + <div class="mermaid-wrapper"> 177 + <pre class="mermaid"> 178 + sequenceDiagram 179 + participant P as Payer's Client 180 + participant R as Recipient 181 + participant S as Payment Servicer 182 + participant B as Browser 183 + 184 + P->>R: Resolve DID document 185 + R-->>P: Service endpoints with #AttestedNetwork DIDs 186 + P->>P: Select servicer from list 187 + 188 + P->>S: network.attested.payment.initiate (authenticated) 189 + Note right of P: Sends product identifier 190 + S-->>P: { token, url } 191 + 192 + P->>B: Redirect payer to URL 193 + B->>S: Complete payment flow 194 + S-->>B: Payment confirmed 195 + 196 + P->>S: network.attested.payment.status (token) 197 + S-->>P: { strongRef } or { failed } 198 + </pre> 199 + </div> 200 + 201 + <h3 style="margin-top: 32px;">Verification</h3> 202 + <p>Any application can verify a payment by checking the cryptographic chain:</p> 203 + <ol class="steps"> 204 + <li>Strip the <code>signatures</code> array from the payment record</li> 205 + <li>Prepare attestation metadata&mdash;add the repository DID, strip <code>cid</code> and <code>signature</code> fields</li> 206 + <li>Insert metadata as the <code>$sig</code> field, serialize to DAG-CBOR</li> 207 + <li>Hash with SHA-256 and wrap as CIDv1 (codec <code>0x71</code>)</li> 208 + <li>Fetch proof records via <code>strongRef</code> URIs and confirm CIDs match</li> 209 + </ol> 210 + 211 + <h3 style="margin-top: 32px;">Trust Models</h3> 212 + <p>Applications can implement different validation strategies:</p> 213 + <ul class="trust-list"> 214 + <li><strong>Strict</strong> <span>Require proofs from both the creator and a specific trusted broker. Highest assurance.</span></li> 215 + <li><strong>Creator-Trusted</strong> <span>Accept any payment the creator has attested. Simpler, trusts creators to vouch for supporters.</span></li> 216 + <li><strong>Federated</strong> <span>Accept attestations from a set of trusted brokers. Enables regional verification networks.</span></li> 217 + </ul> 218 + </section> 219 + 220 + <!-- Lexicon --> 221 + <section id="lexicon"> 222 + <h2>Lexicon</h2> 223 + <p class="section-desc">Four record types and three XRPC methods define the payment specification. Payment records live in the supporter&rsquo;s repository and carry a <code>signatures</code> array referencing proof records from creators and brokers.</p> 224 + 225 + <!-- oneTime --> 226 + <div class="lexicon-block"> 227 + <div class="lexicon-header"> 228 + <div class="lexicon-tag">Record</div> 229 + <div class="lexicon-name">network.attested.payment.oneTime</div> 230 + <p class="lexicon-desc">A one-time payment from a supporter to a creator. Represents a single, non-recurring financial transaction attested by one or more parties.</p> 231 + </div> 232 + <div class="lexicon-body"> 233 + <table class="schema-table"> 234 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 235 + <tbody> 236 + <tr><td class="field-name">subject</td><td class="field-type">string <span class="constraint">(did)</span></td><td class="field-desc">DID of the creator receiving payment</td></tr> 237 + <tr><td class="field-name">amount</td><td class="field-type">integer</td><td class="field-desc">Payment amount in smallest currency unit (e.g. cents). Min: 1</td></tr> 238 + <tr><td class="field-name">currency</td><td class="field-type">string</td><td class="field-desc">ISO 4217 currency code (e.g. <code>USD</code>, <code>EUR</code>)</td></tr> 239 + <tr><td class="field-name">txnid</td><td class="field-type">string</td><td class="field-desc">Unique transaction identifier for deduplication</td></tr> 240 + <tr><td class="field-name">memo</td><td class="field-type">string</td><td class="field-desc">Optional note from the supporter. Max 256 chars</td></tr> 241 + <tr><td class="field-name">createdAt</td><td class="field-type">string <span class="constraint">(datetime)</span></td><td class="field-desc">Timestamp of record creation</td></tr> 242 + <tr><td class="field-name">entitlements</td><td class="field-type">array(<code>com.atproto.repo.strongRef</code>)</td><td class="field-desc">Optional list of strong references to records representing any goods or services the payer is entitled to as a result of this payment</td></tr> 243 + <tr><td class="field-name">signatures</td><td class="field-type">array</td><td class="field-desc">Attestation entries (inline or <code>com.atproto.repo.strongRef</code>)</td></tr> 244 + </tbody> 245 + </table> 246 + </div> 247 + </div> 248 + 249 + <div class="callout"> 250 + <p><strong>Entitlements.</strong> The <code>entitlements</code> field is an optional array of <code>com.atproto.repo.strongRef</code> objects. Each reference points to a record representing a good or service the payer is entitled to as a result of this payment. The referenced records can use any lexicon&mdash;they are not defined by this spec. This allows brokers, recipients, or third parties to define their own product or access records and link them directly to the payment that granted them.</p> 251 + </div> 252 + 253 + <!-- recurring --> 254 + <div class="lexicon-block"> 255 + <div class="lexicon-header"> 256 + <div class="lexicon-tag">Record</div> 257 + <div class="lexicon-name">network.attested.payment.recurring</div> 258 + <p class="lexicon-desc">A recurring payment commitment. Immutable once created&mdash;supporters cancel and create new subscriptions to change terms. Bills on anniversary dates with automatic retry on failure.</p> 259 + </div> 260 + <div class="lexicon-body"> 261 + <table class="schema-table"> 262 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 263 + <tbody> 264 + <tr><td class="field-name">subject</td><td class="field-type">string <span class="constraint">(did)</span></td><td class="field-desc">DID of the creator receiving payment</td></tr> 265 + <tr><td class="field-name">amount</td><td class="field-type">integer</td><td class="field-desc">Amount per billing period in smallest currency unit. Min: 500, Max: 25000 (monthly equivalent)</td></tr> 266 + <tr><td class="field-name">currency</td><td class="field-type">string</td><td class="field-desc">ISO 4217 currency code</td></tr> 267 + <tr><td class="field-name">unit</td><td class="field-type">string</td><td class="field-desc">Billing period: <code>monthly</code> | <code>quarterly</code> | <code>semiannual</code> | <code>yearly</code></td></tr> 268 + <tr><td class="field-name">frequency</td><td class="field-type">integer</td><td class="field-desc">Billing frequency multiplier. Default: 1, Min: 1</td></tr> 269 + <tr><td class="field-name">txnid</td><td class="field-type">string</td><td class="field-desc">Unique transaction identifier for deduplication</td></tr> 270 + <tr><td class="field-name">createdAt</td><td class="field-type">string <span class="constraint">(datetime)</span></td><td class="field-desc">Timestamp of initial subscription creation</td></tr> 271 + <tr><td class="field-name">entitlements</td><td class="field-type">array(<code>com.atproto.repo.strongRef</code>)</td><td class="field-desc">Optional list of strong references to records representing any goods or services the payer is entitled to as a result of this payment</td></tr> 272 + <tr><td class="field-name">signatures</td><td class="field-type">array</td><td class="field-desc">Attestation entries (inline or <code>com.atproto.repo.strongRef</code>)</td></tr> 273 + </tbody> 274 + </table> 275 + </div> 276 + </div> 277 + 278 + <!-- scheduled --> 279 + <div class="lexicon-block"> 280 + <div class="lexicon-header"> 281 + <div class="lexicon-tag">Record</div> 282 + <div class="lexicon-name">network.attested.payment.scheduled</div> 283 + <p class="lexicon-desc">A fixed series of payments. Unlike recurring payments that continue indefinitely, scheduled payments specify a total count and terminate automatically upon completion.</p> 284 + </div> 285 + <div class="lexicon-body"> 286 + <table class="schema-table"> 287 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 288 + <tbody> 289 + <tr><td class="field-name">subject</td><td class="field-type">string <span class="constraint">(did)</span></td><td class="field-desc">DID of the creator receiving payment</td></tr> 290 + <tr><td class="field-name">amount</td><td class="field-type">integer</td><td class="field-desc">Amount per payment in smallest currency unit</td></tr> 291 + <tr><td class="field-name">currency</td><td class="field-type">string</td><td class="field-desc">ISO 4217 currency code</td></tr> 292 + <tr><td class="field-name">unit</td><td class="field-type">string</td><td class="field-desc">Interval: <code>monthly</code> | <code>quarterly</code> | <code>semiannual</code> | <code>yearly</code></td></tr> 293 + <tr><td class="field-name">count</td><td class="field-type">integer</td><td class="field-desc">Total number of scheduled payments. Min: 2, Max: 60</td></tr> 294 + <tr><td class="field-name">txnid</td><td class="field-type">string</td><td class="field-desc">Unique transaction identifier for deduplication</td></tr> 295 + <tr><td class="field-name">createdAt</td><td class="field-type">string <span class="constraint">(datetime)</span></td><td class="field-desc">Timestamp of schedule creation; first payment date</td></tr> 296 + <tr><td class="field-name">entitlements</td><td class="field-type">array(<code>com.atproto.repo.strongRef</code>)</td><td class="field-desc">Optional list of strong references to records representing any goods or services the payer is entitled to as a result of this payment</td></tr> 297 + <tr><td class="field-name">signatures</td><td class="field-type">array</td><td class="field-desc">Attestation entries (inline or <code>com.atproto.repo.strongRef</code>)</td></tr> 298 + </tbody> 299 + </table> 300 + </div> 301 + </div> 302 + 303 + <!-- proof --> 304 + <div class="lexicon-block"> 305 + <div class="lexicon-header"> 306 + <div class="lexicon-tag">Record</div> 307 + <div class="lexicon-name">network.attested.payment.proof</div> 308 + <p class="lexicon-desc">A remote attestation record stored in the attestor&rsquo;s repository (creator or broker). Referenced via <code>com.atproto.repo.strongRef</code> in the payment record&rsquo;s <code>signatures</code> array.</p> 309 + </div> 310 + <div class="lexicon-body"> 311 + <table class="schema-table"> 312 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 313 + <tbody> 314 + <tr><td class="field-name">cid</td><td class="field-type">string <span class="constraint">(cid)</span></td><td class="field-desc">Attestation CID computed per the <a href="https://badge.blue">badge.blue</a> spec from the payment record, metadata, and repository DID</td></tr> 315 + <tr><td class="field-name">status</td><td class="field-type">string</td><td class="field-desc">Optional status indicator for the attestation</td></tr> 316 + </tbody> 317 + </table> 318 + </div> 319 + </div> 320 + 321 + <!-- lookup --> 322 + <div class="lexicon-block"> 323 + <div class="lexicon-header"> 324 + <div class="lexicon-tag">Query (HTTP GET)</div> 325 + <div class="lexicon-name">network.attested.payment.lookup</div> 326 + <p class="lexicon-desc">Look up verified payment records between a payer and recipient. Returns only records whose attestations have been cryptographically verified. Results may include any combination of <code>oneTime</code>, <code>recurring</code>, and <code>scheduled</code> payment records.</p> 327 + </div> 328 + <div class="lexicon-body"> 329 + <h4 style="font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); margin: 20px 0 4px;">Parameters</h4> 330 + <table class="schema-table"> 331 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 332 + <tbody> 333 + <tr><td class="field-name">payer</td><td class="field-type">string <span class="constraint">(did, required)</span></td><td class="field-desc">DID of the payer (supporter)</td></tr> 334 + <tr><td class="field-name">recipient</td><td class="field-type">string <span class="constraint">(did, required)</span></td><td class="field-desc">DID of the recipient (creator)</td></tr> 335 + <tr><td class="field-name">paymentType</td><td class="field-type">string</td><td class="field-desc">Optional filter by payment collection. Must be a full NSID: <code>network.attested.payment.oneTime</code>, <code>network.attested.payment.recurring</code>, or <code>network.attested.payment.scheduled</code></td></tr> 336 + <tr><td class="field-name">brokers</td><td class="field-type">array(string) <span class="constraint">(did, optional, repeating)</span></td><td class="field-desc">Optional list of broker DIDs. When provided, only returns payments that have at least one validating signature from an identity in this list</td></tr> 337 + <tr><td class="field-name">entitlements</td><td class="field-type">array(string) <span class="constraint">(at-uri, optional, repeating)</span></td><td class="field-desc">Optional list of entitlement AT-URIs. When provided, only returns payments whose <code>entitlements</code> array contains one or more of the given references</td></tr> 338 + </tbody> 339 + </table> 340 + 341 + <h4 style="font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); margin: 20px 0 4px;">Response</h4> 342 + <table class="schema-table"> 343 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 344 + <tbody> 345 + <tr><td class="field-name">payments</td><td class="field-type">array(union)</td><td class="field-desc">List of verified payment records. Each element is a union of <code>network.attested.payment.oneTime</code>, <code>network.attested.payment.recurring</code>, or <code>network.attested.payment.scheduled</code></td></tr> 346 + </tbody> 347 + </table> 348 + </div> 349 + </div> 350 + 351 + <!-- initiate --> 352 + <div class="lexicon-block"> 353 + <div class="lexicon-header"> 354 + <div class="lexicon-tag">Procedure (HTTP POST)</div> 355 + <div class="lexicon-name">network.attested.payment.initiate</div> 356 + <p class="lexicon-desc">Begin a payment flow. Called by the payer&rsquo;s client against the selected payment servicer using inter-service authentication. Returns a token for status polling and a URL to direct the payer through the payment process.</p> 357 + </div> 358 + <div class="lexicon-body"> 359 + <h4 style="font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); margin: 20px 0 4px;">Input</h4> 360 + <table class="schema-table"> 361 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 362 + <tbody> 363 + <tr><td class="field-name">product</td><td class="field-type">string <span class="constraint">(required)</span></td><td class="field-desc">Product identifier for the payment being initiated</td></tr> 364 + </tbody> 365 + </table> 366 + 367 + <h4 style="font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); margin: 20px 0 4px;">Response</h4> 368 + <table class="schema-table"> 369 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 370 + <tbody> 371 + <tr><td class="field-name">token</td><td class="field-type">string</td><td class="field-desc">Opaque token used to poll payment status via <code>payment.status</code></td></tr> 372 + <tr><td class="field-name">url</td><td class="field-type">string <span class="constraint">(uri)</span></td><td class="field-desc">URL to direct the payer to in a browser to complete the payment</td></tr> 373 + </tbody> 374 + </table> 375 + </div> 376 + </div> 377 + 378 + <!-- status --> 379 + <div class="lexicon-block"> 380 + <div class="lexicon-header"> 381 + <div class="lexicon-tag">Query (HTTP GET)</div> 382 + <div class="lexicon-name">network.attested.payment.status</div> 383 + <p class="lexicon-desc">Check the status of a payment initiated via <code>payment.initiate</code>. Returns either a <code>com.atproto.repo.strongRef</code> pointing to the completed payment record, or a failed status.</p> 384 + </div> 385 + <div class="lexicon-body"> 386 + <h4 style="font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); margin: 20px 0 4px;">Parameters</h4> 387 + <table class="schema-table"> 388 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 389 + <tbody> 390 + <tr><td class="field-name">token</td><td class="field-type">string <span class="constraint">(required)</span></td><td class="field-desc">Token returned from <code>payment.initiate</code></td></tr> 391 + </tbody> 392 + </table> 393 + 394 + <h4 style="font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); margin: 20px 0 4px;">Response</h4> 395 + <table class="schema-table"> 396 + <thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead> 397 + <tbody> 398 + <tr><td class="field-name">status</td><td class="field-type">string</td><td class="field-desc"><code>pending</code> | <code>completed</code> | <code>failed</code></td></tr> 399 + <tr><td class="field-name">ref</td><td class="field-type">com.atproto.repo.strongRef</td><td class="field-desc">Present when status is <code>completed</code>. Points to the attested payment record in the payer&rsquo;s repository</td></tr> 400 + </tbody> 401 + </table> 402 + </div> 403 + </div> 404 + </section> 405 + 406 + <!-- Examples --> 407 + <section id="examples"> 408 + <h2>Examples</h2> 409 + <p class="section-desc">Complete record examples showing how attested payments work in practice, using <a href="https://badge.blue">badge.blue</a> remote attestations with <code>com.atproto.repo.strongRef</code> entries.</p> 410 + 411 + <!-- Example 1: One-time tip --> 412 + <h3>One-time tip with dual attestation</h3> 413 + <p>A supporter sends a $25 tip to a creator. Both the creator and broker independently attest the payment by writing proof records to their own repositories.</p> 414 + 415 + <div class="code-block"> 416 + <div class="code-label">Supporter's repo &mdash; payment record</div> 417 + <pre><span class="hl-punc">{</span> 418 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.oneTime"</span><span class="hl-punc">,</span> 419 + <span class="hl-key">"subject"</span><span class="hl-punc">:</span> <span class="hl-str">"did:plc:v5jkrb2oncmnhc7rtqhrdzwi"</span><span class="hl-punc">,</span> 420 + <span class="hl-key">"amount"</span><span class="hl-punc">:</span> <span class="hl-num">2500</span><span class="hl-punc">,</span> 421 + <span class="hl-key">"currency"</span><span class="hl-punc">:</span> <span class="hl-str">"USD"</span><span class="hl-punc">,</span> 422 + <span class="hl-key">"txnid"</span><span class="hl-punc">:</span> <span class="hl-str">"01J5K9P3XQHV7WNBCM2G8RFAT"</span><span class="hl-punc">,</span> 423 + <span class="hl-key">"memo"</span><span class="hl-punc">:</span> <span class="hl-str">"Great stream, keep it up!"</span><span class="hl-punc">,</span> 424 + <span class="hl-key">"createdAt"</span><span class="hl-punc">:</span> <span class="hl-str">"2025-07-14T19:22:00.000Z"</span><span class="hl-punc">,</span> 425 + <span class="hl-key">"signatures"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 426 + <span class="hl-punc">{</span> 427 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 428 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:v5jkrb2oncmnhc7rtqhrdzwi/network.attested.payment.proof/3la7qxz2vbc2s"</span><span class="hl-punc">,</span> 429 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreigyh7s6lqf5n3xke4jt6r2x3mqkzf4wpgicbqhqg5k3vdjn7aomfe"</span> 430 + <span class="hl-punc">}</span><span class="hl-punc">,</span> 431 + <span class="hl-punc">{</span> 432 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 433 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:broker-payments-xyz/network.attested.payment.proof/3la7qy2kbrc2t"</span><span class="hl-punc">,</span> 434 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu"</span> 435 + <span class="hl-punc">}</span> 436 + <span class="hl-punc">]</span> 437 + <span class="hl-punc">}</span></pre> 438 + </div> 439 + 440 + <div class="code-block"> 441 + <div class="code-label">Creator's repo &mdash; proof record</div> 442 + <pre><span class="hl-punc">{</span> 443 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.proof"</span><span class="hl-punc">,</span> 444 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreifdw3gy6ef4mcep5forx72cilu6wbsvuajkgsxnluqemkpa5xvzrwu"</span> 445 + <span class="hl-punc">}</span></pre> 446 + </div> 447 + 448 + <div class="callout"> 449 + <p><strong>How the CID binds to the repo:</strong> The proof&rsquo;s <code>cid</code> field is computed from the payment record with the supporter&rsquo;s DID baked into the <code>$sig</code> metadata. If someone copies the payment record to a different repository, recomputing the CID produces a different value&mdash;verification fails automatically.</p> 450 + </div> 451 + 452 + <!-- Example 2: Monthly recurring --> 453 + <h3 style="margin-top: 40px;">Monthly recurring subscription</h3> 454 + <p>A $10/month recurring support commitment. The record is immutable&mdash;to change the amount, the supporter cancels and creates a new subscription.</p> 455 + 456 + <div class="code-block"> 457 + <div class="code-label">Supporter's repo &mdash; recurring payment</div> 458 + <pre><span class="hl-punc">{</span> 459 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.recurring"</span><span class="hl-punc">,</span> 460 + <span class="hl-key">"subject"</span><span class="hl-punc">:</span> <span class="hl-str">"did:plc:v5jkrb2oncmnhc7rtqhrdzwi"</span><span class="hl-punc">,</span> 461 + <span class="hl-key">"amount"</span><span class="hl-punc">:</span> <span class="hl-num">1000</span><span class="hl-punc">,</span> 462 + <span class="hl-key">"currency"</span><span class="hl-punc">:</span> <span class="hl-str">"USD"</span><span class="hl-punc">,</span> 463 + <span class="hl-key">"unit"</span><span class="hl-punc">:</span> <span class="hl-str">"monthly"</span><span class="hl-punc">,</span> 464 + <span class="hl-key">"frequency"</span><span class="hl-punc">:</span> <span class="hl-num">1</span><span class="hl-punc">,</span> 465 + <span class="hl-key">"txnid"</span><span class="hl-punc">:</span> <span class="hl-str">"01J5KBR4WMHV8XNBDM3G9SFBT"</span><span class="hl-punc">,</span> 466 + <span class="hl-key">"createdAt"</span><span class="hl-punc">:</span> <span class="hl-str">"2025-08-01T00:00:00.000Z"</span><span class="hl-punc">,</span> 467 + <span class="hl-key">"entitlements"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 468 + <span class="hl-punc">{</span> 469 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 470 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:v5jkrb2oncmnhc7rtqhrdzwi/com.example.product/3miemuswqbyiw"</span><span class="hl-punc">,</span> 471 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreik3xxgb5tcoqd3ne6gqkvrulzpfnwjmcc5fsgqjdx4huswnhzbekaa"</span> 472 + <span class="hl-punc">}</span> 473 + <span class="hl-punc">]</span><span class="hl-punc">,</span> 474 + <span class="hl-key">"signatures"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 475 + <span class="hl-punc">{</span> 476 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 477 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:v5jkrb2oncmnhc7rtqhrdzwi/network.attested.payment.proof/3lb2rxz3vdc3t"</span><span class="hl-punc">,</span> 478 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreigyh7s6lqf5n3xke4jt6r2x3mqkzf4wpgicbqhqg5k3vdjn7aomfe"</span> 479 + <span class="hl-punc">}</span><span class="hl-punc">,</span> 480 + <span class="hl-punc">{</span> 481 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 482 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:broker-payments-xyz/network.attested.payment.proof/3lb2ry4ldsc3u"</span><span class="hl-punc">,</span> 483 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu"</span> 484 + <span class="hl-punc">}</span> 485 + <span class="hl-punc">]</span> 486 + <span class="hl-punc">}</span></pre> 487 + </div> 488 + 489 + <!-- Example 3: Scheduled payments --> 490 + <h3 style="margin-top: 40px;">Scheduled payment series</h3> 491 + <p>Six monthly payments of $50 each, terminating automatically after the final installment. Useful for project-based sponsorships or fixed commitments.</p> 492 + 493 + <div class="code-block"> 494 + <div class="code-label">Supporter's repo &mdash; scheduled payment</div> 495 + <pre><span class="hl-punc">{</span> 496 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.scheduled"</span><span class="hl-punc">,</span> 497 + <span class="hl-key">"subject"</span><span class="hl-punc">:</span> <span class="hl-str">"did:plc:v5jkrb2oncmnhc7rtqhrdzwi"</span><span class="hl-punc">,</span> 498 + <span class="hl-key">"amount"</span><span class="hl-punc">:</span> <span class="hl-num">5000</span><span class="hl-punc">,</span> 499 + <span class="hl-key">"currency"</span><span class="hl-punc">:</span> <span class="hl-str">"USD"</span><span class="hl-punc">,</span> 500 + <span class="hl-key">"unit"</span><span class="hl-punc">:</span> <span class="hl-str">"monthly"</span><span class="hl-punc">,</span> 501 + <span class="hl-key">"count"</span><span class="hl-punc">:</span> <span class="hl-num">6</span><span class="hl-punc">,</span> 502 + <span class="hl-key">"txnid"</span><span class="hl-punc">:</span> <span class="hl-str">"01J5KCS5XRHW9YOCEN4H0TGCU"</span><span class="hl-punc">,</span> 503 + <span class="hl-key">"createdAt"</span><span class="hl-punc">:</span> <span class="hl-str">"2025-09-01T00:00:00.000Z"</span><span class="hl-punc">,</span> 504 + <span class="hl-key">"signatures"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 505 + <span class="hl-punc">{</span> 506 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 507 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:broker-payments-xyz/network.attested.payment.proof/3lc3syz4wec4u"</span><span class="hl-punc">,</span> 508 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreif4xxgb5tcoqd3ne6gqkvrulzpfnwjmcc5fsgqjdx4huswnhzbekru"</span> 509 + <span class="hl-punc">}</span> 510 + <span class="hl-punc">]</span> 511 + <span class="hl-punc">}</span></pre> 512 + </div> 513 + 514 + <!-- Example 4: Broker proof --> 515 + <h3 style="margin-top: 40px;">Broker proof record</h3> 516 + <p>The broker&rsquo;s independent attestation, stored in their own repository. This is the record referenced by the second <code>strongRef</code> in the examples above.</p> 517 + 518 + <div class="code-block"> 519 + <div class="code-label">Broker's repo &mdash; proof record</div> 520 + <pre><span class="hl-punc">{</span> 521 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.proof"</span><span class="hl-punc">,</span> 522 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu"</span> 523 + <span class="hl-punc">}</span></pre> 524 + </div> 525 + 526 + <!-- Verification flow diagram --> 527 + <h3 style="margin-top: 40px;">Verification flow</h3> 528 + <p>How an application verifies a one-time payment with dual attestation:</p> 529 + 530 + <div class="mermaid-wrapper"> 531 + <pre class="mermaid"> 532 + sequenceDiagram 533 + participant App as Verifying App 534 + participant S as Supporter's Repo 535 + participant C as Creator's Repo 536 + participant B as Broker's Repo 537 + 538 + App->>S: Fetch payment record 539 + S-->>App: payment.oneTime + signatures[] 540 + 541 + par Verify creator attestation 542 + App->>C: Fetch proof via strongRef URI 543 + C-->>App: payment.proof (cid) 544 + App->>App: Recompute CID from payment + supporter DID 545 + App->>App: Confirm CID matches proof.cid 546 + and Verify broker attestation 547 + App->>B: Fetch proof via strongRef URI 548 + B-->>App: payment.proof (cid) 549 + App->>App: Recompute CID from payment + supporter DID 550 + App->>App: Confirm CID matches proof.cid 551 + end 552 + 553 + App->>App: Payment verified ✓ 554 + </pre> 555 + </div> 556 + </section> 557 + 558 + </main> 559 + 560 + <footer> 561 + <div class="container"> 562 + <p>attested.network &middot; Built on <a href="https://badge.blue">badge.blue</a> attestations &middot; <a href="https://atproto.com">ATProtocol</a></p> 563 + </div> 564 + </footer> 565 + 566 + </body> 567 + </html>
+125
payers.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Payers — attested.network</title> 7 + <meta name="description" content="Guide for making attested payments on ATProtocol."> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> 11 + <link rel="stylesheet" href="styles.css"> 12 + </head> 13 + <body> 14 + 15 + <div class="draft-banner"> 16 + <p><strong>Draft &mdash; Not yet published.</strong> This specification is in active development and is not ready for review or critique. Stay tuned for formal announcements.</p> 17 + </div> 18 + 19 + <div class="topnav"> 20 + <div class="topnav-inner"> 21 + <a class="topnav-logo" href="/"><span>attested</span>.network</a> 22 + <nav> 23 + <ul class="topnav-links"> 24 + <li><a href="index.html">Spec</a></li> 25 + <li><a href="brokers.html">Brokers</a></li> 26 + <li><a href="app-developers.html">App Developers</a></li> 27 + <li><a href="recipients.html">Recipients</a></li> 28 + <li><a href="payers.html" class="active">Payers</a></li> 29 + <li><a href="scenarios.html">Scenarios</a></li> 30 + </ul> 31 + </nav> 32 + </div> 33 + </div> 34 + 35 + <div class="hero-sm"> 36 + <div class="hero-inner"> 37 + <div class="breadcrumb"><a href="index.html">Home</a> / Payers</div> 38 + <h1>Making <span>Payments</span></h1> 39 + <p class="subtitle">How your payments are recorded, verified, and portable across ATProtocol.</p> 40 + </div> 41 + </div> 42 + 43 + <main class="container"> 44 + 45 + <!-- How Your Payments Work --> 46 + <section> 47 + <h2>How Your Payments Work</h2> 48 + <p class="section-desc">A simple system with strong guarantees.</p> 49 + <p>When you pay to support a creator, a payment record is written to your ATProtocol repository. This isn't just a receipt &mdash; it's a cryptographic proof that independent parties can verify.</p> 50 + <p>The creator and a payment servicer (called a broker) both write their own confirmation records. The broker facilitates and witnesses the exchange&mdash;whether that&rsquo;s a credit card transaction, a cash payment, or any other mechanism. Together, these three records form an unbreakable chain of proof.</p> 51 + <p>Because all three records are independently signed and stored, no single party can falsify or revoke your payment history. Your proof stands on its own.</p> 52 + </section> 53 + 54 + <!-- The Payment Experience --> 55 + <section> 56 + <h2>The Payment Experience</h2> 57 + <p class="section-desc">What you actually see when you make a payment.</p> 58 + <ol class="steps"> 59 + <li>You find a creator you want to support.</li> 60 + <li>Your app shows you the available payment methods &mdash; these are the creator's preferred servicers.</li> 61 + <li>You choose a servicer and select your payment type: a one-time tip, a recurring subscription, or a scheduled series of payments.</li> 62 + <li>You're directed to a payment page where you enter your details and confirm.</li> 63 + <li>Once complete, the proof appears in your account &mdash; a verified record you own forever.</li> 64 + </ol> 65 + </section> 66 + 67 + <!-- Payment Types --> 68 + <section> 69 + <h2>Payment Types</h2> 70 + <p class="section-desc">Three ways to support the people you care about.</p> 71 + <div class="card-grid"> 72 + <div class="card"> 73 + <h4>One-time</h4> 74 + <p>A single tip or donation in any amount. Perfect for saying thanks, supporting a specific piece of work, or trying out a creator before committing to ongoing support.</p> 75 + </div> 76 + <div class="card"> 77 + <h4>Recurring</h4> 78 + <p>An ongoing subscription that renews automatically. Choose monthly, quarterly, semi-annual, or yearly. Recurring payments are immutable &mdash; to change the amount, cancel the current one and start fresh.</p> 79 + </div> 80 + <div class="card"> 81 + <h4>Scheduled</h4> 82 + <p>A fixed number of payments spread over time &mdash; for example, 6 monthly payments. The series ends automatically when all payments are complete. No need to remember to cancel.</p> 83 + </div> 84 + </div> 85 + </section> 86 + 87 + <!-- Your Data, Your Records --> 88 + <section> 89 + <h2>Your Data, Your Records</h2> 90 + <p class="section-desc">You own your payment history. Always.</p> 91 + <p>Payment records live in your repository, not in a company's database. You own them. They're signed with your identity and stored under your control.</p> 92 + <p>If you switch apps or platforms, your payment history comes with you. No export needed, no data request forms, no waiting. Your records are already yours.</p> 93 + <p>No platform can lock you out of your own payment records. Even if a service shuts down, your proof of payment remains intact and verifiable.</p> 94 + </section> 95 + 96 + <!-- Privacy Options --> 97 + <section> 98 + <h2>Privacy Options</h2> 99 + <p class="section-desc">You decide who sees your payment activity.</p> 100 + <div class="callout"> 101 + <p><strong>Public payments</strong> are visible in your repository for anyone to verify. This is great for transparency &mdash; creators can showcase their supporters, and you can show your support publicly.</p> 102 + <p style="margin-top: 12px;"><strong>Private payments</strong> are stored in a Permissioned Data Space where only authorized parties can see them. The creator and broker can still verify the payment, but it won't appear in your public repository.</p> 103 + <p style="margin-top: 12px;">Both options use the same verification mechanics. Private payments aren't less trustworthy &mdash; they're just less visible. Choose whichever feels right for you.</p> 104 + </div> 105 + </section> 106 + 107 + <!-- Cancellation & Changes --> 108 + <section> 109 + <h2>Cancellation &amp; Changes</h2> 110 + <p class="section-desc">Simple, predictable rules for managing your payments.</p> 111 + <p>When you cancel a recurring or scheduled payment, the cancellation is immediate and intentional. There's no ambiguity &mdash; a cancellation record is written to confirm it.</p> 112 + <p>Active recurring payments complete the current period before ending. If you've already paid for this month, you keep your access through the end of that period.</p> 113 + <p>Payment records are immutable &mdash; you can't edit one after it's created. If you want different terms, cancel the existing payment and start a new one. This keeps your payment history clean and auditable.</p> 114 + <p>Pre-paid support (like a scheduled series you've already funded) continues through the funded period even after cancellation. You've already paid for it, so you keep it.</p> 115 + </section> 116 + 117 + </main> 118 + 119 + <footer> 120 + <div class="container"> 121 + <p>attested.network &middot; Built on <a href="https://badge.blue">badge.blue</a> attestations &middot; <a href="https://atproto.com">ATProtocol</a></p> 122 + </div> 123 + </footer> 124 + </body> 125 + </html>
+196
recipients.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Recipients — attested.network</title> 7 + <meta name="description" content="Guide for setting up your identity to accept attested payments on ATProtocol."> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> 11 + <link rel="stylesheet" href="styles.css"> 12 + <script type="module"> 13 + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; 14 + mermaid.initialize({ 15 + startOnLoad: true, 16 + theme: 'base', 17 + themeVariables: { 18 + primaryColor: '#f5f3ff', 19 + primaryTextColor: '#111827', 20 + primaryBorderColor: '#ddd6fe', 21 + secondaryColor: '#f8f9fb', 22 + tertiaryColor: '#ffffff', 23 + lineColor: '#8b5cf6', 24 + textColor: '#111827', 25 + mainBkg: '#f5f3ff', 26 + nodeBorder: '#ddd6fe', 27 + clusterBkg: '#faf9fc', 28 + clusterBorder: '#e5e7eb', 29 + fontFamily: 'Inter, -apple-system, system-ui, sans-serif', 30 + fontSize: '13px' 31 + } 32 + }); 33 + </script> 34 + </head> 35 + <body> 36 + 37 + <div class="draft-banner"> 38 + <p><strong>Draft &mdash; Not yet published.</strong> This specification is in active development and is not ready for review or critique. Stay tuned for formal announcements.</p> 39 + </div> 40 + 41 + <div class="topnav"> 42 + <div class="topnav-inner"> 43 + <a class="topnav-logo" href="/"><span>attested</span>.network</a> 44 + <nav> 45 + <ul class="topnav-links"> 46 + <li><a href="index.html">Spec</a></li> 47 + <li><a href="brokers.html">Brokers</a></li> 48 + <li><a href="app-developers.html">App Developers</a></li> 49 + <li><a href="recipients.html" class="active">Recipients</a></li> 50 + <li><a href="payers.html">Payers</a></li> 51 + <li><a href="scenarios.html">Scenarios</a></li> 52 + </ul> 53 + </nav> 54 + </div> 55 + </div> 56 + 57 + <div class="hero-sm"> 58 + <div class="hero-inner"> 59 + <div class="breadcrumb"><a href="index.html">Home</a> / Recipients</div> 60 + <h1>Receiving <span>Payments</span></h1> 61 + <p class="subtitle">Set up your identity to accept attested payments from supporters.</p> 62 + </div> 63 + </div> 64 + 65 + <main class="container"> 66 + 67 + <!-- How It Works --> 68 + <section id="how-it-works"> 69 + <h2>How It Works</h2> 70 + <p class="section-desc">When someone supports you through an attested payment, three things happen simultaneously&mdash;creating a verifiable record that no single party controls.</p> 71 + 72 + <p>A <strong>payment record</strong> is written to your supporter&rsquo;s repository, declaring that they made a payment. A <strong>proof record</strong> is written to your repository (by the broker acting on your behalf), confirming you received it. And the <strong>broker writes their own proof</strong> to their repository, witnessing the exchange.</p> 73 + 74 + <p>This three-way structure is what makes attested payments powerful. Any application on ATProtocol can look at these three independent records and verify the support relationship&mdash;without asking permission from any platform or payment processor.</p> 75 + 76 + <div class="mermaid-wrapper"> 77 + <pre class="mermaid"> 78 + graph LR 79 + S["Supporter pays"] --> PAY["Payment Record<br/><i>Supporter's repo</i>"] 80 + PAY --> CP["Proof<br/><i>Your repo</i>"] 81 + PAY --> BP["Proof<br/><i>Broker's repo</i>"] 82 + </pre> 83 + </div> 84 + 85 + <div class="callout"> 86 + <p><strong>Why three records?</strong> If only your supporter had a record, they could fabricate payments. If only you had a record, you could fabricate supporters. The broker&rsquo;s independent proof ties it all together&mdash;and because all three are on ATProtocol, any app can verify the relationship without trusting any single party.</p> 87 + </div> 88 + </section> 89 + 90 + <!-- Choosing Payment Servicers --> 91 + <section id="choosing-servicers"> 92 + <h2>Choosing Payment Servicers</h2> 93 + <p class="section-desc">You signal which payment servicers (brokers) you use by publishing an ordered list in your DID document.</p> 94 + 95 + <p>Brokers handle the actual payment processing&mdash;credit cards, bank transfers, and other payment methods&mdash;so you don&rsquo;t have to. You publish an ordered list of broker DIDs, each referencing their <code>#AttestedNetwork</code> service endpoint. The order matters: it represents your preference ranking, so apps know which servicer to try first.</p> 96 + 97 + <p>You can use <strong>multiple brokers simultaneously</strong>. This gives your supporters options and protects you from relying on a single provider. If one broker goes down or raises fees, your other servicers continue working immediately.</p> 98 + 99 + <p>The ecosystem is designed for competition and choice. Brokers can be regional (focused on local payment methods), non-profit (community-run cooperatives), crypto-native (accepting digital currencies), or traditional payment processors. You pick the ones that work for you and your audience.</p> 100 + 101 + <p>Your choice of brokers also affects discoverability. Apps can query for payments that have been validated by specific brokers&mdash;so using well-known, trusted servicers means your payment proofs are more likely to be recognized and accepted across the ecosystem.</p> 102 + 103 + <div class="callout"> 104 + <p><strong>No lock-in.</strong> Switching brokers is as simple as updating the list in your DID document. Your existing payment history and proofs remain valid regardless of which servicers you use going forward.</p> 105 + </div> 106 + </section> 107 + 108 + <!-- Setting Up --> 109 + <section id="setting-up"> 110 + <h2>Setting Up</h2> 111 + <p class="section-desc">Getting started takes three steps. Your chosen servicer handles everything else.</p> 112 + 113 + <ol class="steps"> 114 + <li><strong>Choose one or more payment servicers (brokers).</strong> Browse available brokers and pick the ones that support the payment methods your audience uses. You can start with one and add more later.</li> 115 + <li><strong>Add their DIDs to your DID document.</strong> Publish an ordered list of broker DIDs, each referencing their <code>#AttestedNetwork</code> service endpoint. The order signals your preference&mdash;apps will try brokers in the order you list them.</li> 116 + <li><strong>Configure your products or tiers with each servicer.</strong> This happens on the servicer&rsquo;s platform. Set up your pricing, subscription tiers, or tip amounts. The servicer will guide you through their onboarding process.</li> 117 + </ol> 118 + 119 + <p>That&rsquo;s it. Once your DID document lists your servicers and you&rsquo;ve configured your offerings, supporters can start paying you through any ATProtocol app that supports attested payments.</p> 120 + </section> 121 + 122 + <!-- What Happens When Someone Pays --> 123 + <section id="payment-flow"> 124 + <h2>What Happens When Someone Pays</h2> 125 + <p class="section-desc">Here&rsquo;s what the flow looks like from your perspective when a supporter decides to pay you.</p> 126 + 127 + <div class="mermaid-wrapper"> 128 + <pre class="mermaid"> 129 + sequenceDiagram 130 + participant App as Supporter's App 131 + participant DID as Your DID Document 132 + participant Svc as Payment Servicer 133 + participant SR as Supporter's Repo 134 + participant YR as Your Repo 135 + participant BR as Broker's Repo 136 + 137 + App->>DID: Resolve your DID document 138 + DID-->>App: Return servicer list 139 + App->>Svc: Initiate payment with preferred servicer 140 + Svc->>Svc: Process payment 141 + Svc->>SR: Write payment record 142 + Svc->>YR: Write proof to your repo 143 + Svc->>BR: Write proof to broker's repo 144 + Note over SR,BR: All three records are now verifiable by any app 145 + </pre> 146 + </div> 147 + 148 + <p>The key thing to notice is that <strong>you don&rsquo;t have to do anything</strong> once you&rsquo;re set up. The servicer handles the payment, writes the records, and everything is automatically verifiable. You don&rsquo;t need to be online, run a server, or respond to webhooks.</p> 149 + </section> 150 + 151 + <!-- Payment Types --> 152 + <section id="payment-types"> 153 + <h2>Payment Types</h2> 154 + <p class="section-desc">Attested payments support three types, each suited to different creator needs.</p> 155 + 156 + <div class="card-grid"> 157 + <div class="card"> 158 + <h4>One-time</h4> 159 + <p>Single tips, donations, or one-off purchases. A supporter pays once and receives a permanent, verifiable proof of that payment. Great for tipping on individual posts or making a one-time contribution.</p> 160 + </div> 161 + <div class="card"> 162 + <h4>Recurring</h4> 163 + <p>Ongoing subscriptions that renew automatically&mdash;monthly, quarterly, or at any interval you choose. The servicer handles renewals and writes fresh proofs each period. Ideal for ongoing creator support or membership access.</p> 164 + </div> 165 + <div class="card"> 166 + <h4>Scheduled</h4> 167 + <p>A fixed series of payments with a defined end. For example, six monthly payments to sponsor a project, or four quarterly payments for a course. The total commitment is clear upfront for both you and your supporter.</p> 168 + </div> 169 + </div> 170 + </section> 171 + 172 + <!-- Portability --> 173 + <section id="portability"> 174 + <h2>Portability</h2> 175 + <p class="section-desc">Your supporter relationships belong to you and your supporters&mdash;not to any platform.</p> 176 + 177 + <p>Every payment record and proof lives on ATProtocol, not in a proprietary database. This means your entire payment history&mdash;who supports you, when they started, what they&rsquo;ve contributed&mdash;travels with you across the network.</p> 178 + 179 + <p>If you <strong>switch apps</strong>, your new app can read the same records and display your supporters immediately. If you <strong>change servicers</strong>, your existing payment history remains intact and verifiable. Active subscriptions continue to be honored because the proof records are independent of which servicer processed them.</p> 180 + 181 + <p>This is a fundamental departure from traditional platforms where your subscriber list, payment history, and supporter relationships are locked inside a single service. On ATProtocol, those relationships are yours.</p> 182 + 183 + <div class="callout"> 184 + <p><strong>Built on open infrastructure.</strong> Attested payments use <a href="https://badge.blue">badge.blue</a> attestations and <a href="https://atproto.com">ATProtocol</a> repositories. Every record is content-addressed and cryptographically bound to its repository&mdash;portable, verifiable, and owned by the people who created them.</p> 185 + </div> 186 + </section> 187 + 188 + </main> 189 + 190 + <footer> 191 + <div class="container"> 192 + <p>attested.network &middot; Built on <a href="https://badge.blue">badge.blue</a> attestations &middot; <a href="https://atproto.com">ATProtocol</a></p> 193 + </div> 194 + </footer> 195 + </body> 196 + </html>
+283
scenarios.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Scenarios — attested.network</title> 7 + <meta name="description" content="Real-world usage scenarios for attested payments on ATProtocol."> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> 11 + <link rel="stylesheet" href="styles.css"> 12 + <script type="module"> 13 + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; 14 + mermaid.initialize({ 15 + startOnLoad: true, 16 + theme: 'base', 17 + themeVariables: { 18 + primaryColor: '#f5f3ff', 19 + primaryTextColor: '#111827', 20 + primaryBorderColor: '#ddd6fe', 21 + secondaryColor: '#f8f9fb', 22 + tertiaryColor: '#ffffff', 23 + lineColor: '#8b5cf6', 24 + textColor: '#111827', 25 + mainBkg: '#f5f3ff', 26 + nodeBorder: '#ddd6fe', 27 + clusterBkg: '#faf9fc', 28 + clusterBorder: '#e5e7eb', 29 + fontFamily: 'Inter, -apple-system, system-ui, sans-serif', 30 + fontSize: '13px' 31 + } 32 + }); 33 + </script> 34 + </head> 35 + <body> 36 + 37 + <div class="draft-banner"> 38 + <p><strong>Draft &mdash; Not yet published.</strong> This specification is in active development and is not ready for review or critique. Stay tuned for formal announcements.</p> 39 + </div> 40 + 41 + <!-- Top navigation --> 42 + <div class="topnav"> 43 + <div class="topnav-inner"> 44 + <a class="topnav-logo" href="/"><span>attested</span>.network</a> 45 + <nav> 46 + <ul class="topnav-links"> 47 + <li><a href="index.html">Spec</a></li> 48 + <li><a href="brokers.html">Brokers</a></li> 49 + <li><a href="app-developers.html">App Developers</a></li> 50 + <li><a href="recipients.html">Recipients</a></li> 51 + <li><a href="payers.html">Payers</a></li> 52 + <li><a href="scenarios.html" class="active">Scenarios</a></li> 53 + </ul> 54 + </nav> 55 + </div> 56 + </div> 57 + 58 + <!-- Hero --> 59 + <div class="hero-sm"> 60 + <div class="hero-inner"> 61 + <div class="breadcrumb"><a href="index.html">Home</a> / Scenarios</div> 62 + <h1>Example <span>Scenarios</span></h1> 63 + <p class="subtitle">Real-world usage patterns showing how attested payments work end-to-end.</p> 64 + </div> 65 + </div> 66 + 67 + <main class="container"> 68 + 69 + <!-- Podcast Subscription --> 70 + <section id="podcast-subscription"> 71 + <h2>Podcast Subscription</h2> 72 + <p class="section-desc">A podcast offers a $10/month premium subscription. Supporters pay through a broker, which creates a recurring payment record with an entitlement that grants access to premium content. Any app, tile, or appview can verify the subscription by checking for an active payment with the matching entitlement.</p> 73 + 74 + <h3>How it works</h3> 75 + <p>The podcast creator publishes their content on ATProtocol and uses a broker to handle subscriptions. When a listener subscribes, three things happen:</p> 76 + 77 + <ol class="steps"> 78 + <li>The listener&rsquo;s client initiates a payment through the broker via <code>network.attested.payment.initiate</code></li> 79 + <li>The broker processes the $10/month payment and writes a <code>network.attested.payment.recurring</code> record to the listener&rsquo;s repository, including an <code>entitlements</code> reference pointing to the podcast&rsquo;s product record</li> 80 + <li>The broker and creator each write <code>payment.proof</code> attestation records to their own repositories, linked via the payment&rsquo;s <code>signatures</code> array</li> 81 + </ol> 82 + 83 + <p>From that point on, any app can verify the subscription by calling <code>network.attested.payment.lookup</code> with the entitlement AT-URI. No centralized subscription database needed.</p> 84 + 85 + <div class="mermaid-wrapper"> 86 + <pre class="mermaid"> 87 + sequenceDiagram 88 + participant L as Listener's Client 89 + participant B as Broker 90 + participant LR as Listener's Repo 91 + participant CR as Creator's Repo 92 + participant BR as Broker's Repo 93 + participant App as Podcast App 94 + 95 + L->>B: network.attested.payment.initiate 96 + B-->>L: { token, url } 97 + L->>B: Complete payment ($10/month) 98 + 99 + B->>LR: Write payment.recurring record 100 + Note right of LR: Includes entitlements[]<br/>pointing to product record 101 + 102 + par Attestations 103 + B->>BR: Write payment.proof 104 + B->>CR: Notify creator 105 + CR->>CR: Write payment.proof 106 + end 107 + 108 + App->>B: network.attested.payment.lookup<br/>?payer=...&recipient=...<br/>&paymentType=...recurring<br/>&entitlements=at://...product/... 109 + B-->>App: { payments: [recurring record] } 110 + App->>App: Subscription verified, grant access 111 + </pre> 112 + </div> 113 + 114 + <h3 style="margin-top: 40px;">The payment record</h3> 115 + <p>The recurring payment record lives in the listener&rsquo;s repository. It declares the $10/month commitment, references the podcast&rsquo;s product record as an entitlement, and carries attestation signatures from both the creator and broker.</p> 116 + 117 + <div class="code-block"> 118 + <div class="code-label">Listener's repo &mdash; recurring payment</div> 119 + <pre><span class="hl-punc">{</span> 120 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.recurring"</span><span class="hl-punc">,</span> 121 + <span class="hl-key">"subject"</span><span class="hl-punc">:</span> <span class="hl-str">"did:plc:podcast-creator"</span><span class="hl-punc">,</span> 122 + <span class="hl-key">"amount"</span><span class="hl-punc">:</span> <span class="hl-num">1000</span><span class="hl-punc">,</span> 123 + <span class="hl-key">"currency"</span><span class="hl-punc">:</span> <span class="hl-str">"USD"</span><span class="hl-punc">,</span> 124 + <span class="hl-key">"unit"</span><span class="hl-punc">:</span> <span class="hl-str">"monthly"</span><span class="hl-punc">,</span> 125 + <span class="hl-key">"frequency"</span><span class="hl-punc">:</span> <span class="hl-num">1</span><span class="hl-punc">,</span> 126 + <span class="hl-key">"txnid"</span><span class="hl-punc">:</span> <span class="hl-str">"01J6M4R5XQHV8WNBCM3G9RFBT"</span><span class="hl-punc">,</span> 127 + <span class="hl-key">"createdAt"</span><span class="hl-punc">:</span> <span class="hl-str">"2026-03-01T00:00:00.000Z"</span><span class="hl-punc">,</span> 128 + <span class="hl-key">"entitlements"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 129 + <span class="hl-punc">{</span> 130 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 131 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:podcast-creator/com.example.podcast.subscription/premium"</span><span class="hl-punc">,</span> 132 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreig7xxgb5tcoqd3ne6gqkvrulzpfnwjmcc5fsgqjdx4huswnhzbekcc"</span> 133 + <span class="hl-punc">}</span> 134 + <span class="hl-punc">]</span><span class="hl-punc">,</span> 135 + <span class="hl-key">"signatures"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 136 + <span class="hl-punc">{</span> 137 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 138 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:podcast-creator/network.attested.payment.proof/3la8rxz3vdc4t"</span><span class="hl-punc">,</span> 139 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreigyh7s6lqf5n3xke4jt6r2x3mqkzf4wpgicbqhqg5k3vdjn7aomfe"</span> 140 + <span class="hl-punc">}</span><span class="hl-punc">,</span> 141 + <span class="hl-punc">{</span> 142 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 143 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:broker-payments/network.attested.payment.proof/3la8ry4ldsc4u"</span><span class="hl-punc">,</span> 144 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu"</span> 145 + <span class="hl-punc">}</span> 146 + <span class="hl-punc">]</span> 147 + <span class="hl-punc">}</span></pre> 148 + </div> 149 + 150 + <h3 style="margin-top: 40px;">Verifying access</h3> 151 + <p>When a listener opens the podcast in any ATProtocol app, the app checks for an active subscription by querying the broker&rsquo;s lookup endpoint. The key is filtering by both the recurring payment type and the specific entitlement.</p> 152 + 153 + <div class="code-block"> 154 + <div class="code-label">Lookup request</div> 155 + <pre><span class="hl-comment">// Does this listener have an active premium subscription?</span> 156 + GET /xrpc/network.attested.payment.lookup 157 + <span class="hl-punc">?</span><span class="hl-key">payer</span><span class="hl-punc">=</span><span class="hl-str">did:plc:listener123</span> 158 + <span class="hl-punc">&amp;</span><span class="hl-key">recipient</span><span class="hl-punc">=</span><span class="hl-str">did:plc:podcast-creator</span> 159 + <span class="hl-punc">&amp;</span><span class="hl-key">paymentType</span><span class="hl-punc">=</span><span class="hl-str">network.attested.payment.recurring</span> 160 + <span class="hl-punc">&amp;</span><span class="hl-key">entitlements</span><span class="hl-punc">=</span><span class="hl-str">at://did:plc:podcast-creator/com.example.podcast.subscription/premium</span></pre> 161 + </div> 162 + 163 + <p>If the response contains a payment record, the listener has an active subscription and the app grants access to premium episodes. If the <code>payments</code> array is empty, the listener either hasn&rsquo;t subscribed or their subscription has lapsed.</p> 164 + 165 + <div class="callout"> 166 + <p><strong>App-agnostic verification.</strong> Because the payment record and its entitlements live on the protocol, any podcast app, tile, or appview can independently verify the subscription. The listener isn&rsquo;t locked into a single client&mdash;their subscription follows them across the ecosystem.</p> 167 + </div> 168 + 169 + <div class="callout" style="margin-top: 16px;"> 170 + <p><strong>Entitlements are flexible.</strong> The <code>com.example.podcast.subscription/premium</code> record is defined by the podcast creator, not by this spec. It could contain tier details, feature flags, or access rules&mdash;whatever makes sense for the product. The payment record simply references it as a <code>strongRef</code>, linking proof of payment to what was purchased.</p> 171 + </div> 172 + </section> 173 + 174 + <!-- One-Time Purchase --> 175 + <section id="one-time-purchase"> 176 + <h2>One-Time Purchase</h2> 177 + <p class="section-desc">A user makes a one-time payment to unlock something tied to a specific recipient&mdash;a profile badge in a social app, a tip that comes with an award, downloadable content, or any other digital good. The payment is linked to an entitlement record that the app checks to gate access or display.</p> 178 + 179 + <h3>How it works</h3> 180 + <p>A user wants to support a creator and get something in return&mdash;maybe a supporter badge that appears on their profile, or access to bonus content. The app presents the option, the user pays through a broker, and the resulting payment record carries an entitlement that any app can verify.</p> 181 + 182 + <ol class="steps"> 183 + <li>The user selects a purchase option in the app (e.g. &ldquo;Buy Supporter Badge&rdquo; or &ldquo;Unlock Bonus Pack&rdquo;). The app initiates the payment via <code>network.attested.payment.initiate</code> on the creator&rsquo;s broker</li> 184 + <li>The broker processes the one-time payment and writes a <code>network.attested.payment.oneTime</code> record to the payer&rsquo;s repository. The record includes an <code>entitlements</code> reference pointing to the specific product&mdash;a badge, an award, DLC content, or whatever the creator has defined</li> 185 + <li>The broker and creator each write <code>payment.proof</code> attestation records to their own repositories, completing the cryptographic chain</li> 186 + </ol> 187 + 188 + <p>When any app wants to check whether the user has purchased that item, it calls <code>network.attested.payment.lookup</code> filtered by the entitlement AT-URI. If a matching one-time payment exists, the user has paid and the app renders the badge, unlocks the content, or grants whatever the entitlement represents.</p> 189 + 190 + <div class="mermaid-wrapper"> 191 + <pre class="mermaid"> 192 + sequenceDiagram 193 + participant U as User's Client 194 + participant B as Broker 195 + participant UR as User's Repo 196 + participant CR as Creator's Repo 197 + participant BR as Broker's Repo 198 + participant App as Social App 199 + 200 + U->>B: network.attested.payment.initiate 201 + B-->>U: { token, url } 202 + U->>B: Complete payment (one-time) 203 + 204 + B->>UR: Write payment.oneTime record 205 + Note right of UR: Includes entitlements[]<br/>pointing to badge/product record 206 + 207 + par Attestations 208 + B->>BR: Write payment.proof 209 + B->>CR: Notify creator 210 + CR->>CR: Write payment.proof 211 + end 212 + 213 + App->>B: network.attested.payment.lookup<br/>?payer=...&recipient=...<br/>&entitlements=at://...badge/... 214 + B-->>App: { payments: [oneTime record] } 215 + App->>App: Show badge on profile 216 + </pre> 217 + </div> 218 + 219 + <h3 style="margin-top: 40px;">The payment record</h3> 220 + <p>The one-time payment record lives in the payer&rsquo;s repository. In this example, a user pays $5 to get a supporter badge on a creator&rsquo;s profile. The entitlement references the creator&rsquo;s badge record.</p> 221 + 222 + <div class="code-block"> 223 + <div class="code-label">Payer's repo &mdash; one-time payment</div> 224 + <pre><span class="hl-punc">{</span> 225 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"network.attested.payment.oneTime"</span><span class="hl-punc">,</span> 226 + <span class="hl-key">"subject"</span><span class="hl-punc">:</span> <span class="hl-str">"did:plc:creator-xyz"</span><span class="hl-punc">,</span> 227 + <span class="hl-key">"amount"</span><span class="hl-punc">:</span> <span class="hl-num">500</span><span class="hl-punc">,</span> 228 + <span class="hl-key">"currency"</span><span class="hl-punc">:</span> <span class="hl-str">"USD"</span><span class="hl-punc">,</span> 229 + <span class="hl-key">"txnid"</span><span class="hl-punc">:</span> <span class="hl-str">"01J7N5S6YRHW0XPBDN4H1UHEV"</span><span class="hl-punc">,</span> 230 + <span class="hl-key">"memo"</span><span class="hl-punc">:</span> <span class="hl-str">"Supporter badge"</span><span class="hl-punc">,</span> 231 + <span class="hl-key">"createdAt"</span><span class="hl-punc">:</span> <span class="hl-str">"2026-03-20T14:30:00.000Z"</span><span class="hl-punc">,</span> 232 + <span class="hl-key">"entitlements"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 233 + <span class="hl-punc">{</span> 234 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 235 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:creator-xyz/com.example.app.badge/supporter"</span><span class="hl-punc">,</span> 236 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreif8xxgb6tcoqd4ne7gqkvrulzpfnwjmcc6fsgqjdx5huswnhzbekdd"</span> 237 + <span class="hl-punc">}</span> 238 + <span class="hl-punc">]</span><span class="hl-punc">,</span> 239 + <span class="hl-key">"signatures"</span><span class="hl-punc">:</span> <span class="hl-punc">[</span> 240 + <span class="hl-punc">{</span> 241 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 242 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:creator-xyz/network.attested.payment.proof/3ld4txz5xfe5v"</span><span class="hl-punc">,</span> 243 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreigyh8s7lqf6n4xke5jt7r3x4mqkzf5wpgicbqhqg6k4vdjn8bomge"</span> 244 + <span class="hl-punc">}</span><span class="hl-punc">,</span> 245 + <span class="hl-punc">{</span> 246 + <span class="hl-key">"$type"</span><span class="hl-punc">:</span> <span class="hl-str">"com.atproto.repo.strongRef"</span><span class="hl-punc">,</span> 247 + <span class="hl-key">"uri"</span><span class="hl-punc">:</span> <span class="hl-str">"at://did:plc:broker-payments/network.attested.payment.proof/3ld4ty5mdtc5w"</span><span class="hl-punc">,</span> 248 + <span class="hl-key">"cid"</span><span class="hl-punc">:</span> <span class="hl-str">"bafyreih8wwfa4tcoqd3ne6gqkvrulzpfnwjmcc6fsgqjdx5huswnhzafiqu"</span> 249 + <span class="hl-punc">}</span> 250 + <span class="hl-punc">]</span> 251 + <span class="hl-punc">}</span></pre> 252 + </div> 253 + 254 + <h3 style="margin-top: 40px;">Verifying the purchase</h3> 255 + <p>When an app renders a user&rsquo;s profile, it checks whether the user has purchased the supporter badge for that creator by querying the lookup endpoint with the entitlement AT-URI.</p> 256 + 257 + <div class="code-block"> 258 + <div class="code-label">Lookup request</div> 259 + <pre><span class="hl-comment">// Does this user have the supporter badge for this creator?</span> 260 + GET /xrpc/network.attested.payment.lookup 261 + <span class="hl-punc">?</span><span class="hl-key">payer</span><span class="hl-punc">=</span><span class="hl-str">did:plc:user456</span> 262 + <span class="hl-punc">&amp;</span><span class="hl-key">recipient</span><span class="hl-punc">=</span><span class="hl-str">did:plc:creator-xyz</span> 263 + <span class="hl-punc">&amp;</span><span class="hl-key">paymentType</span><span class="hl-punc">=</span><span class="hl-str">network.attested.payment.oneTime</span> 264 + <span class="hl-punc">&amp;</span><span class="hl-key">entitlements</span><span class="hl-punc">=</span><span class="hl-str">at://did:plc:creator-xyz/com.example.app.badge/supporter</span></pre> 265 + </div> 266 + 267 + <p>If the <code>payments</code> array contains a matching record, the app displays the badge on the user&rsquo;s profile. Because this is a one-time payment, it persists indefinitely&mdash;there&rsquo;s no renewal to check.</p> 268 + 269 + <div class="callout"> 270 + <p><strong>Many forms, same pattern.</strong> This scenario covers any one-time digital purchase: supporter badges, tip awards, DLC content packs, custom emoji sets, premium filters, or exclusive stickers. The entitlement record is defined by the app or creator&mdash;the payment spec just links proof of payment to whatever was purchased. Different apps can define different products, all verified the same way.</p> 271 + </div> 272 + </section> 273 + 274 + </main> 275 + 276 + <footer> 277 + <div class="container"> 278 + <p>attested.network &middot; Built on <a href="https://badge.blue">badge.blue</a> attestations &middot; <a href="https://atproto.com">ATProtocol</a></p> 279 + </div> 280 + </footer> 281 + 282 + </body> 283 + </html>
+627
styles.css
··· 1 + :root { 2 + --bg: #ffffff; 3 + --bg-subtle: #f8f9fb; 4 + --bg-code: #f4f5f7; 5 + --surface: #ffffff; 6 + --border: #e5e7eb; 7 + --border-light: #f0f1f3; 8 + --text: #111827; 9 + --text-secondary: #4b5563; 10 + --text-tertiary: #9ca3af; 11 + --accent: #6d28d9; 12 + --accent-light: #8b5cf6; 13 + --accent-bg: #f5f3ff; 14 + --accent-border: #ddd6fe; 15 + --green: #059669; 16 + --green-bg: #ecfdf5; 17 + --blue: #2563eb; 18 + --orange: #d97706; 19 + --mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; 20 + --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; 21 + } 22 + 23 + * { margin: 0; padding: 0; box-sizing: border-box; } 24 + 25 + body { 26 + font-family: var(--sans); 27 + background: var(--bg); 28 + color: var(--text); 29 + line-height: 1.7; 30 + -webkit-font-smoothing: antialiased; 31 + } 32 + 33 + /* Top nav bar */ 34 + .topnav { 35 + position: sticky; 36 + top: 0; 37 + z-index: 100; 38 + background: rgba(255, 255, 255, 0.85); 39 + backdrop-filter: blur(12px); 40 + border-bottom: 1px solid var(--border); 41 + padding: 0 24px; 42 + } 43 + 44 + .topnav-inner { 45 + max-width: 1100px; 46 + margin: 0 auto; 47 + display: flex; 48 + align-items: center; 49 + justify-content: space-between; 50 + height: 56px; 51 + gap: 24px; 52 + } 53 + 54 + .topnav-logo { 55 + font-weight: 700; 56 + font-size: 1rem; 57 + color: var(--text); 58 + text-decoration: none; 59 + flex-shrink: 0; 60 + } 61 + 62 + .topnav-logo span { color: var(--accent); } 63 + 64 + .topnav-links { 65 + display: flex; 66 + gap: 24px; 67 + list-style: none; 68 + overflow-x: auto; 69 + -webkit-overflow-scrolling: touch; 70 + scrollbar-width: none; 71 + } 72 + 73 + .topnav-links::-webkit-scrollbar { display: none; } 74 + 75 + .topnav-links a { 76 + font-size: 0.85rem; 77 + font-weight: 500; 78 + color: var(--text-secondary); 79 + text-decoration: none; 80 + transition: color 0.15s; 81 + white-space: nowrap; 82 + } 83 + 84 + .topnav-links a:hover { color: var(--accent); } 85 + .topnav-links a.active { color: var(--accent); } 86 + 87 + /* Hero */ 88 + .hero { 89 + text-align: center; 90 + padding: 80px 24px 64px; 91 + background: linear-gradient(180deg, var(--accent-bg) 0%, var(--bg) 100%); 92 + border-bottom: 1px solid var(--border-light); 93 + } 94 + 95 + .hero h1 { 96 + font-size: 3rem; 97 + font-weight: 700; 98 + letter-spacing: -0.04em; 99 + line-height: 1.15; 100 + margin-bottom: 16px; 101 + } 102 + 103 + .hero h1 span { color: var(--accent); } 104 + 105 + .hero .subtitle { 106 + font-size: 1.2rem; 107 + color: var(--text-secondary); 108 + max-width: 560px; 109 + margin: 0 auto 32px; 110 + line-height: 1.6; 111 + } 112 + 113 + .hero-badges { 114 + display: flex; 115 + gap: 12px; 116 + justify-content: center; 117 + flex-wrap: wrap; 118 + } 119 + 120 + .hero-badge { 121 + display: inline-flex; 122 + align-items: center; 123 + gap: 6px; 124 + font-size: 0.85rem; 125 + font-weight: 500; 126 + color: var(--accent); 127 + background: var(--surface); 128 + border: 1px solid var(--accent-border); 129 + border-radius: 8px; 130 + padding: 8px 16px; 131 + text-decoration: none; 132 + transition: background 0.15s, border-color 0.15s; 133 + } 134 + 135 + .hero-badge:hover { 136 + background: var(--accent-bg); 137 + border-color: var(--accent-light); 138 + } 139 + 140 + .hero-badge svg { 141 + width: 16px; 142 + height: 16px; 143 + } 144 + 145 + /* Small hero for sub-pages */ 146 + .hero-sm { 147 + text-align: left; 148 + padding: 48px 24px 40px; 149 + background: linear-gradient(180deg, var(--accent-bg) 0%, var(--bg) 100%); 150 + border-bottom: 1px solid var(--border-light); 151 + } 152 + 153 + .hero-sm .hero-inner { 154 + max-width: 860px; 155 + margin: 0 auto; 156 + } 157 + 158 + .hero-sm h1 { 159 + font-size: 2rem; 160 + font-weight: 700; 161 + letter-spacing: -0.03em; 162 + line-height: 1.2; 163 + margin-bottom: 8px; 164 + } 165 + 166 + .hero-sm h1 span { color: var(--accent); } 167 + 168 + .hero-sm .subtitle { 169 + font-size: 1.05rem; 170 + color: var(--text-secondary); 171 + max-width: 560px; 172 + line-height: 1.6; 173 + margin: 0; 174 + } 175 + 176 + .hero-sm .breadcrumb { 177 + font-size: 0.82rem; 178 + color: var(--text-tertiary); 179 + margin-bottom: 12px; 180 + } 181 + 182 + .hero-sm .breadcrumb a { 183 + color: var(--text-tertiary); 184 + text-decoration: none; 185 + } 186 + 187 + .hero-sm .breadcrumb a:hover { color: var(--accent); } 188 + 189 + /* Main content */ 190 + .container { 191 + max-width: 860px; 192 + margin: 0 auto; 193 + padding: 0 24px; 194 + } 195 + 196 + section { 197 + padding: 64px 0; 198 + } 199 + 200 + section + section { 201 + border-top: 1px solid var(--border-light); 202 + } 203 + 204 + h2 { 205 + font-size: 1.75rem; 206 + font-weight: 700; 207 + letter-spacing: -0.03em; 208 + margin-bottom: 12px; 209 + } 210 + 211 + .section-desc { 212 + font-size: 1.05rem; 213 + color: var(--text-secondary); 214 + margin-bottom: 32px; 215 + max-width: 640px; 216 + } 217 + 218 + h3 { 219 + font-size: 1.15rem; 220 + font-weight: 600; 221 + margin-bottom: 8px; 222 + } 223 + 224 + p { 225 + color: var(--text-secondary); 226 + margin-bottom: 16px; 227 + } 228 + 229 + a { color: var(--accent); text-decoration: none; } 230 + a:hover { text-decoration: underline; } 231 + 232 + /* Mermaid */ 233 + .mermaid-wrapper { 234 + background: var(--bg-subtle); 235 + border: 1px solid var(--border); 236 + border-radius: 12px; 237 + padding: 32px 16px; 238 + margin: 32px 0; 239 + overflow-x: auto; 240 + } 241 + 242 + /* Card grid */ 243 + .card-grid { 244 + display: grid; 245 + grid-template-columns: repeat(2, 1fr); 246 + gap: 16px; 247 + margin-top: 28px; 248 + } 249 + 250 + @media (max-width: 640px) { 251 + .card-grid { grid-template-columns: 1fr; } 252 + } 253 + 254 + .card { 255 + background: var(--surface); 256 + border: 1px solid var(--border); 257 + border-radius: 12px; 258 + padding: 24px; 259 + transition: border-color 0.15s, box-shadow 0.15s; 260 + } 261 + 262 + .card:hover { 263 + border-color: var(--accent-border); 264 + box-shadow: 0 2px 12px rgba(109, 40, 217, 0.06); 265 + } 266 + 267 + .card h4 { 268 + font-size: 0.95rem; 269 + font-weight: 600; 270 + margin-bottom: 6px; 271 + } 272 + 273 + .card p { 274 + font-size: 0.88rem; 275 + color: var(--text-secondary); 276 + margin-bottom: 0; 277 + line-height: 1.55; 278 + } 279 + 280 + /* Guide cards (landing page audience selector) */ 281 + .guide-grid { 282 + display: grid; 283 + grid-template-columns: repeat(2, 1fr); 284 + gap: 16px; 285 + margin-top: 20px; 286 + } 287 + 288 + @media (max-width: 640px) { 289 + .guide-grid { grid-template-columns: 1fr; } 290 + } 291 + 292 + .guide-card { 293 + display: block; 294 + background: var(--surface); 295 + border: 1px solid var(--border); 296 + border-left: 3px solid var(--accent); 297 + border-radius: 12px; 298 + padding: 24px 24px 24px 22px; 299 + text-decoration: none; 300 + transition: border-color 0.15s, box-shadow 0.15s; 301 + } 302 + 303 + .guide-card:hover { 304 + border-color: var(--accent-border); 305 + border-left-color: var(--accent-light); 306 + box-shadow: 0 2px 12px rgba(109, 40, 217, 0.08); 307 + text-decoration: none; 308 + } 309 + 310 + .guide-card h4 { 311 + font-size: 0.95rem; 312 + font-weight: 600; 313 + color: var(--text); 314 + margin-bottom: 4px; 315 + } 316 + 317 + .guide-card p { 318 + font-size: 0.85rem; 319 + color: var(--text-secondary); 320 + margin-bottom: 0; 321 + line-height: 1.5; 322 + } 323 + 324 + .guide-card .guide-arrow { 325 + display: inline-block; 326 + color: var(--accent); 327 + font-size: 0.82rem; 328 + font-weight: 500; 329 + margin-top: 10px; 330 + } 331 + 332 + /* Lexicon blocks */ 333 + .lexicon-block { 334 + background: var(--surface); 335 + border: 1px solid var(--border); 336 + border-radius: 12px; 337 + margin-bottom: 24px; 338 + overflow: hidden; 339 + } 340 + 341 + .lexicon-header { 342 + padding: 24px 28px 20px; 343 + border-bottom: 1px solid var(--border-light); 344 + } 345 + 346 + .lexicon-tag { 347 + display: inline-block; 348 + font-size: 0.7rem; 349 + font-weight: 600; 350 + font-family: var(--mono); 351 + color: var(--accent); 352 + background: var(--accent-bg); 353 + border: 1px solid var(--accent-border); 354 + border-radius: 6px; 355 + padding: 2px 8px; 356 + margin-bottom: 8px; 357 + text-transform: uppercase; 358 + letter-spacing: 0.05em; 359 + } 360 + 361 + .lexicon-name { 362 + font-family: var(--mono); 363 + font-size: 1.05rem; 364 + font-weight: 600; 365 + color: var(--text); 366 + margin-bottom: 6px; 367 + word-break: break-all; 368 + } 369 + 370 + .lexicon-desc { 371 + font-size: 0.92rem; 372 + color: var(--text-secondary); 373 + margin-bottom: 0; 374 + line-height: 1.55; 375 + } 376 + 377 + .lexicon-body { 378 + padding: 0 28px 24px; 379 + } 380 + 381 + /* Schema table */ 382 + .schema-table { 383 + width: 100%; 384 + border-collapse: collapse; 385 + font-size: 0.875rem; 386 + margin: 20px 0 0; 387 + } 388 + 389 + .schema-table th { 390 + text-align: left; 391 + font-weight: 600; 392 + font-size: 0.72rem; 393 + text-transform: uppercase; 394 + letter-spacing: 0.06em; 395 + color: var(--text-tertiary); 396 + padding: 10px 12px; 397 + border-bottom: 1px solid var(--border); 398 + } 399 + 400 + .schema-table td { 401 + padding: 10px 12px; 402 + border-bottom: 1px solid var(--border-light); 403 + vertical-align: top; 404 + } 405 + 406 + .schema-table tr:last-child td { border-bottom: none; } 407 + 408 + .field-name { 409 + font-family: var(--mono); 410 + font-weight: 500; 411 + font-size: 0.84rem; 412 + color: var(--accent); 413 + white-space: nowrap; 414 + } 415 + 416 + .field-type { 417 + font-family: var(--mono); 418 + font-size: 0.8rem; 419 + color: var(--green); 420 + white-space: nowrap; 421 + } 422 + 423 + .field-type .constraint { 424 + color: var(--orange); 425 + font-size: 0.72rem; 426 + font-weight: 500; 427 + } 428 + 429 + .field-desc { 430 + color: var(--text-secondary); 431 + font-size: 0.85rem; 432 + } 433 + 434 + /* Inline code */ 435 + code { 436 + font-family: var(--mono); 437 + font-size: 0.85em; 438 + background: var(--bg-code); 439 + color: var(--accent); 440 + padding: 2px 6px; 441 + border-radius: 4px; 442 + border: 1px solid var(--border-light); 443 + } 444 + 445 + /* Code blocks */ 446 + .code-block { 447 + border: 1px solid var(--border); 448 + border-radius: 10px; 449 + overflow: hidden; 450 + margin: 20px 0; 451 + } 452 + 453 + .code-label { 454 + font-size: 0.72rem; 455 + font-family: var(--mono); 456 + font-weight: 500; 457 + color: var(--text-tertiary); 458 + padding: 10px 16px; 459 + background: var(--bg-subtle); 460 + border-bottom: 1px solid var(--border); 461 + text-transform: uppercase; 462 + letter-spacing: 0.05em; 463 + } 464 + 465 + pre { 466 + padding: 16px; 467 + font-family: var(--mono); 468 + font-size: 0.82rem; 469 + line-height: 1.65; 470 + overflow-x: auto; 471 + background: var(--bg-code); 472 + margin: 0; 473 + } 474 + 475 + .hl-key { color: #8b5cf6; } 476 + .hl-str { color: #059669; } 477 + .hl-num { color: #d97706; } 478 + .hl-punc { color: #6b7280; } 479 + .hl-comment { color: #9ca3af; font-style: italic; } 480 + 481 + /* Info callout */ 482 + .callout { 483 + background: var(--accent-bg); 484 + border: 1px solid var(--accent-border); 485 + border-radius: 10px; 486 + padding: 20px 24px; 487 + margin: 24px 0; 488 + } 489 + 490 + .callout p { color: var(--text-secondary); margin-bottom: 0; } 491 + .callout strong { color: var(--text); } 492 + 493 + /* Steps */ 494 + .steps { 495 + counter-reset: step; 496 + padding-left: 0; 497 + list-style: none; 498 + } 499 + 500 + .steps li { 501 + counter-increment: step; 502 + position: relative; 503 + padding-left: 40px; 504 + margin-bottom: 16px; 505 + color: var(--text-secondary); 506 + font-size: 0.95rem; 507 + } 508 + 509 + .steps li::before { 510 + content: counter(step); 511 + position: absolute; 512 + left: 0; 513 + top: 1px; 514 + width: 26px; 515 + height: 26px; 516 + border-radius: 50%; 517 + background: var(--accent-bg); 518 + border: 1px solid var(--accent-border); 519 + color: var(--accent); 520 + font-size: 0.78rem; 521 + font-weight: 600; 522 + display: flex; 523 + align-items: center; 524 + justify-content: center; 525 + line-height: 1; 526 + } 527 + 528 + /* Trust model list */ 529 + .trust-list { 530 + list-style: none; 531 + margin-top: 20px; 532 + } 533 + 534 + .trust-list li { 535 + padding: 16px 20px; 536 + border: 1px solid var(--border); 537 + border-radius: 10px; 538 + margin-bottom: 12px; 539 + background: var(--surface); 540 + } 541 + 542 + .trust-list li strong { 543 + color: var(--text); 544 + display: block; 545 + margin-bottom: 2px; 546 + font-size: 0.92rem; 547 + } 548 + 549 + .trust-list li span { 550 + color: var(--text-secondary); 551 + font-size: 0.88rem; 552 + } 553 + 554 + /* Section sub-heading helper */ 555 + .sub-heading { font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); margin: 20px 0 4px; } 556 + 557 + /* Draft banner */ 558 + .draft-banner { 559 + background: #fef3c7; 560 + border-bottom: 2px solid #f59e0b; 561 + padding: 16px 24px; 562 + text-align: center; 563 + } 564 + 565 + .draft-banner p { 566 + color: #92400e; 567 + font-size: 0.9rem; 568 + font-weight: 500; 569 + margin: 0; 570 + max-width: 720px; 571 + margin: 0 auto; 572 + line-height: 1.5; 573 + } 574 + 575 + .draft-banner strong { 576 + color: #78350f; 577 + font-weight: 700; 578 + text-transform: uppercase; 579 + letter-spacing: 0.03em; 580 + } 581 + 582 + /* Footer */ 583 + footer { 584 + border-top: 1px solid var(--border); 585 + padding: 40px 24px; 586 + text-align: center; 587 + } 588 + 589 + footer p { 590 + font-size: 0.85rem; 591 + color: var(--text-tertiary); 592 + } 593 + 594 + footer a { color: var(--text-tertiary); } 595 + footer a:hover { color: var(--accent); } 596 + 597 + /* Responsive */ 598 + @media (max-width: 640px) { 599 + .hero { padding: 48px 24px 40px; } 600 + .hero h1 { font-size: 2rem; } 601 + .hero .subtitle { font-size: 1rem; } 602 + .hero-sm h1 { font-size: 1.5rem; } 603 + .lexicon-block { overflow: visible; } 604 + .lexicon-header, .lexicon-body { padding-left: 16px; padding-right: 16px; } 605 + .lexicon-name { font-size: 0.82rem; overflow-wrap: break-word; word-break: break-word; } 606 + .topnav-links { gap: 16px; } 607 + .topnav-links a { font-size: 0.78rem; } 608 + .card-grid, .guide-grid { grid-template-columns: 1fr; } 609 + 610 + /* Stack schema tables on mobile */ 611 + .schema-table thead { display: none; } 612 + .schema-table, .schema-table tbody { display: block; } 613 + .schema-table tr { 614 + display: block; 615 + padding: 12px 0; 616 + border-bottom: 1px solid var(--border-light); 617 + } 618 + .schema-table tr:last-child { border-bottom: none; } 619 + .schema-table td { 620 + display: block; 621 + padding: 2px 0; 622 + border-bottom: none; 623 + } 624 + .field-name { font-size: 0.88rem; } 625 + .field-type { white-space: normal; font-size: 0.76rem; } 626 + .field-desc { font-size: 0.82rem; margin-top: 4px; } 627 + }