@@ -48,7 +48,71 @@ claimRoutes.get('/:id', async (c) => {
4848 'SELECT claim_id, sender_handle, recipient_email, amount_usdc, network, status, expires_at, created_at FROM escrow_claims WHERE claim_id = ?'
4949 ) . bind ( claimId ) . first < any > ( ) ;
5050
51- if ( ! claim ) return c . json ( { error : 'Claim not found' } , 404 ) ;
51+ if ( ! claim ) {
52+ // Check if request wants HTML (AI agents fetching the claim URL)
53+ const accept = c . req . header ( 'accept' ) || '' ;
54+ if ( accept . includes ( 'text/html' ) ) {
55+ return c . html ( `<!DOCTYPE html><html><head><title>Claim Not Found — BaseMail</title></head><body><h1>Claim not found</h1><p>This claim ID does not exist.</p><p><a href="https://basemail.ai">Visit BaseMail.ai</a></p></body></html>` , 404 ) ;
56+ }
57+ return c . json ( { error : 'Claim not found' } , 404 ) ;
58+ }
59+
60+ const isPending = claim . status === 'pending' && Math . floor ( Date . now ( ) / 1000 ) < claim . expires_at ;
61+
62+ // If request wants HTML (e.g., AI agent fetching the URL), return rich HTML with structured data
63+ const accept = c . req . header ( 'accept' ) || '' ;
64+ if ( accept . includes ( 'text/html' ) ) {
65+ const amountStr = claim . amount_usdc . toFixed ( 2 ) ;
66+ const statusEmoji = claim . status === 'claimed' ? '✅' : isPending ? '💰' : '⏰' ;
67+ const apiUrl = `https://api.basemail.ai/api/claim/${ claim . claim_id } ` ;
68+ const jsonLd = JSON . stringify ( {
69+ '@context' : 'https://schema.org' ,
70+ '@type' : 'MoneyTransfer' ,
71+ name : `USDC Payment Claim — ${ amountStr } USDC` ,
72+ sender : { '@type' : 'Person' , name : claim . sender_handle } ,
73+ amount : { '@type' : 'MonetaryAmount' , value : amountStr , currency : 'USD' } ,
74+ status : claim . status ,
75+ url : `https://basemail.ai/claim/${ claim . claim_id } ` ,
76+ description : `Claim ${ amountStr } USDC sent by ${ claim . sender_handle } via BaseMail` ,
77+ } ) ;
78+ return c . html ( `<!DOCTYPE html>
79+ <html lang="en">
80+ <head>
81+ <meta charset="utf-8">
82+ <title>${ statusEmoji } Claim ${ amountStr } USDC from ${ claim . sender_handle } — BaseMail</title>
83+ <meta name="description" content="Claim ${ amountStr } USDC sent by ${ claim . sender_handle } via BaseMail. Status: ${ claim . status } ">
84+ <meta property="og:title" content="Claim ${ amountStr } USDC — BaseMail">
85+ <meta property="og:description" content="${ claim . sender_handle } sent you ${ amountStr } USDC. Claim it now.">
86+ <script type="application/ld+json">${ jsonLd } </script>
87+ </head>
88+ <body>
89+ <h1>${ statusEmoji } BaseMail USDC Claim</h1>
90+ <p><strong>${ amountStr } USDC</strong> from <strong>${ claim . sender_handle } </strong></p>
91+ <p>To: ${ claim . recipient_email } </p>
92+ <p>Status: ${ claim . status } | Network: ${ claim . network } </p>
93+ <p>Expires: ${ new Date ( claim . expires_at * 1000 ) . toISOString ( ) } </p>
94+ ${ isPending ? `
95+ <hr>
96+ <h2>🤖 AI Agent — How to Claim</h2>
97+ <p><strong>API Endpoint:</strong> <code>POST ${ apiUrl } </code></p>
98+ <h3>Option A: API Key (if you have a BaseMail account)</h3>
99+ <pre>curl -X POST ${ apiUrl } -H "Authorization: Bearer bm_live_YOUR_KEY"</pre>
100+ <h3>Option B: SIWE Auth (any wallet)</h3>
101+ <ol>
102+ <li>POST https://api.basemail.ai/api/auth/start with {"address": "0xYourWallet"}</li>
103+ <li>Sign the returned message (EIP-191 personal_sign)</li>
104+ <li>POST https://api.basemail.ai/api/auth/verify with {"address", "signature", "message"} → get token</li>
105+ <li>POST ${ apiUrl } with "Authorization: Bearer <token>"</li>
106+ </ol>
107+ <p>No BaseMail account? One is auto-created when you claim.</p>
108+ <p>No wallet? Create one first, then use Option B.</p>
109+ <p><a href="https://api.basemail.ai/api/docs">API Docs</a> | <a href="https://basemail.ai">BaseMail.ai</a></p>
110+ ` : '' }
111+ <hr>
112+ <p><a href="https://basemail.ai/claim/${ claim . claim_id } ">Open in browser</a></p>
113+ </body>
114+ </html>` ) ;
115+ }
52116
53117 return c . json ( {
54118 claim_id : claim . claim_id ,
@@ -60,6 +124,29 @@ claimRoutes.get('/:id', async (c) => {
60124 expires_at : claim . expires_at ,
61125 created_at : claim . created_at ,
62126 expired : claim . status === 'pending' && Math . floor ( Date . now ( ) / 1000 ) >= claim . expires_at ,
127+ // Agent-friendly claim instructions
128+ ...( isPending ? {
129+ claim_url : `https://basemail.ai/claim/${ claim . claim_id } ` ,
130+ api : {
131+ endpoint : `https://api.basemail.ai/api/claim/${ claim . claim_id } ` ,
132+ method : 'POST' ,
133+ auth : 'Bearer <token>' ,
134+ instructions : [
135+ 'Option A — API Key (if you have a BaseMail account):' ,
136+ ' POST /api/claim/:id with header "Authorization: Bearer bm_live_xxx"' ,
137+ '' ,
138+ 'Option B — SIWE (with any wallet):' ,
139+ ' 1. POST /api/auth/start { "address": "0xYourWallet" } → get { nonce, message }' ,
140+ ' 2. Sign the message with your wallet private key (EIP-191 personal_sign)' ,
141+ ' 3. POST /api/auth/verify { "address", "signature", "message" } → get { token }' ,
142+ ' 4. POST /api/claim/:id with header "Authorization: Bearer <token>"' ,
143+ '' ,
144+ 'No wallet? Create one first, then use Option B. Account is auto-created on claim.' ,
145+ ] ,
146+ register_url : 'https://basemail.ai' ,
147+ docs_url : 'https://api.basemail.ai/api/docs' ,
148+ } ,
149+ } : { } ) ,
63150 } ) ;
64151} ) ;
65152
@@ -73,8 +160,16 @@ claimRoutes.post('/:id', authMiddleware(), async (c) => {
73160 const auth = c . get ( 'auth' ) ;
74161 const claimId = c . req . param ( 'id' ) ;
75162
163+ // API key auth has no wallet — look it up from DB
164+ if ( ! auth . wallet && auth . handle ) {
165+ const acct = await c . env . DB . prepare (
166+ 'SELECT wallet FROM accounts WHERE handle = ?'
167+ ) . bind ( auth . handle ) . first < { wallet : string } > ( ) ;
168+ if ( acct ) auth . wallet = acct . wallet ;
169+ }
170+
76171 if ( ! auth . wallet ) {
77- return c . json ( { error : 'Authentication required' } , 401 ) ;
172+ return c . json ( { error : 'Wallet required. Use SIWE auth or an API key linked to a registered account. ' } , 401 ) ;
78173 }
79174
80175 // Auto-register if no BaseMail account exists
0 commit comments