22 * VerifiedFeedbackDialog - DualSignature feedback demonstrating blind feedback model
33 *
44 * 2-Step Flow:
5- * 1. INTERACT: User pays $0.01 USDC → Agent signs BLINDLY (doesn't know outcome)
5+ * 1. INTERACT: User pays $0.001 USDC → Agent signs BLINDLY (doesn't know outcome)
66 * 2. RATE & SUBMIT: User chooses outcome → Signs SIWS → Server submits transaction
77 *
88 * This demonstrates SATI's core innovation: unforgeable feedback where the agent
@@ -21,8 +21,9 @@ import {
2121 buildCounterpartyMessage ,
2222 serializeFeedback ,
2323 type FeedbackData ,
24+ handleTransactionError ,
2425} from "@cascade-fyi/sati-sdk" ;
25- import { getNetwork , getSolscanUrl , getRpcUrl } from "@/lib/network" ;
26+ import { getChain , getNetwork , getSolscanUrl , getRpcUrl } from "@/lib/network" ;
2627import { createPaymentFetch } from "@/lib/x402" ;
2728
2829import { Button } from "@/components/ui/button" ;
@@ -43,11 +44,24 @@ import { Loader2, CheckCircle2 } from "lucide-react";
4344const deployedConfig = loadDeployedConfig ( getNetwork ( ) ) ;
4445const FEEDBACK_SCHEMA_ADDRESS = deployedConfig ?. schemas ?. feedback as Address | undefined ;
4546
47+ // Pre-filled agent signature data (for skipping Step 1)
48+ interface PrefilledSignatureData {
49+ taskRef : string ; // hex
50+ dataHash : string ; // hex
51+ agentSignature : string ; // hex
52+ agentOwner : string ;
53+ }
54+
4655interface VerifiedFeedbackDialogProps {
4756 agentMint : Address ;
4857 agentName : string ;
49- children : ReactNode ;
58+ children ? : ReactNode ;
5059 onSuccess ?: ( ) => void ;
60+ // Controlled mode props
61+ open ?: boolean ;
62+ onOpenChange ?: ( open : boolean ) => void ;
63+ // Pre-filled data to skip Step 1 (agent already signed)
64+ prefilledData ?: PrefilledSignatureData ;
5165}
5266
5367// API types
@@ -61,7 +75,7 @@ interface EchoRequest {
6175interface EchoResponse {
6276 success : boolean ;
6377 data ?: {
64- agentAddress : string ;
78+ agentOwner : string ;
6579 interactionHash : string ;
6680 signature : string ;
6781 signatureBase58 : string ;
@@ -78,7 +92,7 @@ interface SubmitFeedbackRequest {
7892 outcome : number ;
7993 counterparty : string ;
8094 agentSignature : string ;
81- agentAddress : string ;
95+ agentOwner : string ;
8296 counterpartySignature : string ;
8397 counterpartyMessage : string ;
8498 content ?: string ;
@@ -117,18 +131,40 @@ interface AgentSignatureData {
117131 taskRef : string ; // hex
118132 dataHash : string ; // hex
119133 signature : string ; // hex
120- agentAddress : string ;
134+ agentOwner : string ;
121135}
122136
123137type Step = "interact" | "rate" ;
124138
125- export function VerifiedFeedbackDialog ( { agentMint, agentName, children, onSuccess } : VerifiedFeedbackDialogProps ) {
126- const [ open , setOpen ] = useState ( false ) ;
139+ export function VerifiedFeedbackDialog ( {
140+ agentMint,
141+ agentName,
142+ children,
143+ onSuccess,
144+ open : controlledOpen ,
145+ onOpenChange : controlledOnOpenChange ,
146+ prefilledData,
147+ } : VerifiedFeedbackDialogProps ) {
148+ // Support controlled mode
149+ const [ internalOpen , setInternalOpen ] = useState ( false ) ;
150+ const isControlled = controlledOpen !== undefined ;
151+ const open = isControlled ? controlledOpen : internalOpen ;
152+
153+ // Initialize step based on prefilledData
154+ const initialStep : Step = prefilledData ? "rate" : "interact" ;
155+ const initialAgentData : AgentSignatureData | null = prefilledData
156+ ? {
157+ taskRef : prefilledData . taskRef ,
158+ dataHash : prefilledData . dataHash ,
159+ signature : prefilledData . agentSignature ,
160+ agentOwner : prefilledData . agentOwner ,
161+ }
162+ : null ;
127163
128164 // Step state
129- const [ step , setStep ] = useState < Step > ( "interact" ) ;
165+ const [ step , setStep ] = useState < Step > ( initialStep ) ;
130166 const [ isInteracting , setIsInteracting ] = useState ( false ) ;
131- const [ agentSignatureData , setAgentSignatureData ] = useState < AgentSignatureData | null > ( null ) ;
167+ const [ agentSignatureData , setAgentSignatureData ] = useState < AgentSignatureData | null > ( initialAgentData ) ;
132168
133169 // Rating state (Step 2)
134170 const [ outcome , setOutcome ] = useState < string > ( "2" ) ; // Default to Positive
@@ -139,10 +175,15 @@ export function VerifiedFeedbackDialog({ agentMint, agentName, children, onSucce
139175
140176 // Reset state when dialog closes
141177 const handleOpenChange = ( isOpen : boolean ) => {
142- setOpen ( isOpen ) ;
178+ if ( isControlled ) {
179+ controlledOnOpenChange ?.( isOpen ) ;
180+ } else {
181+ setInternalOpen ( isOpen ) ;
182+ }
143183 if ( ! isOpen ) {
144- setStep ( "interact" ) ;
145- setAgentSignatureData ( null ) ;
184+ // Reset to initial state (which depends on prefilledData)
185+ setStep ( initialStep ) ;
186+ setAgentSignatureData ( initialAgentData ) ;
146187 setOutcome ( "2" ) ;
147188 setMessage ( "" ) ;
148189 setIsInteracting ( false ) ;
@@ -177,9 +218,9 @@ export function VerifiedFeedbackDialog({ agentMint, agentName, children, onSucce
177218 const taskRef = bytesToHex ( new Uint8Array ( taskRefDigest ) ) ;
178219 const dataHash = bytesToHex ( new Uint8Array ( dataHashDigest ) ) ;
179220
180- // Create x402 payment-enabled fetch
181- toast . loading ( "Approve payment ($0.01 USDC)..." , { id : toastId } ) ;
182- const rpcUrl = getRpcUrl ( getNetwork ( ) ) ;
221+ // Create x402 payment-enabled fetch using user's network
222+ toast . loading ( "Approve payment ($0.001 USDC)..." , { id : toastId } ) ;
223+ const rpcUrl = getRpcUrl ( getChain ( ) ) ;
183224 const paymentFetch = createPaymentFetch ( session , rpcUrl ) ;
184225
185226 // Call /api/echo with x402 payment - agent signs WITHOUT knowing outcome
@@ -211,7 +252,7 @@ export function VerifiedFeedbackDialog({ agentMint, agentName, children, onSucce
211252 taskRef,
212253 dataHash,
213254 signature : echoResult . data . signature ,
214- agentAddress : echoResult . data . agentAddress ,
255+ agentOwner : echoResult . data . agentOwner ,
215256 } ) ;
216257
217258 toast . success ( "Agent signed! Now choose your rating." , { id : toastId } ) ;
@@ -289,7 +330,7 @@ export function VerifiedFeedbackDialog({ agentMint, agentName, children, onSucce
289330 outcome : selectedOutcome ,
290331 counterparty : session . account . address ,
291332 agentSignature : agentSignatureData . signature ,
292- agentAddress : agentSignatureData . agentAddress ,
333+ agentOwner : agentSignatureData . agentOwner ,
293334 counterpartySignature : bytesToHex ( counterpartySig ) ,
294335 counterpartyMessage : bytesToHex ( new Uint8Array ( siwsMessage . messageBytes ) ) ,
295336 ...( contentJson && { content : contentJson , contentType : 1 } ) ,
@@ -329,8 +370,18 @@ export function VerifiedFeedbackDialog({ agentMint, agentName, children, onSucce
329370 } ;
330371 } catch ( error ) {
331372 toast . dismiss ( toastId ) ;
332- const errorMessage = error instanceof Error ? error . message : "Unknown error" ;
333- toast . error ( `Failed: ${ errorMessage } ` ) ;
373+
374+ // Use SDK error handler for consistent error messages
375+ const result = handleTransactionError ( error ) ;
376+
377+ if ( result . reason === "duplicate_attestation" ) {
378+ toast . error ( result . message , {
379+ description : "Each prediction can only receive one feedback per user." ,
380+ duration : 6000 ,
381+ } ) ;
382+ } else {
383+ toast . error ( result . message ) ;
384+ }
334385 throw error ;
335386 }
336387 } ,
@@ -358,7 +409,7 @@ export function VerifiedFeedbackDialog({ agentMint, agentName, children, onSucce
358409
359410 return (
360411 < Dialog open = { open } onOpenChange = { handleOpenChange } >
361- < DialogTrigger asChild > { children } </ DialogTrigger >
412+ { children && < DialogTrigger asChild > { children } </ DialogTrigger > }
362413 < DialogContent className = "sm:max-w-md" >
363414 < DialogHeader >
364415 < DialogTitle > Verified Feedback</ DialogTitle >
@@ -396,9 +447,9 @@ export function VerifiedFeedbackDialog({ agentMint, agentName, children, onSucce
396447 { step === "interact" && (
397448 < div className = "space-y-4 py-4" >
398449 < p className = "text-sm text-muted-foreground" >
399- Pay < span className = "font-medium text-foreground" > $0.01 USDC</ span > to request a signature from the agent.
400- The agent signs < span className = "font-medium text-foreground" > without knowing</ span > what feedback you'll
401- give.
450+ Pay < span className = "font-medium text-foreground" > $0.001 USDC</ span > to request a signature from the
451+ agent. The agent signs < span className = "font-medium text-foreground" > without knowing</ span > what feedback
452+ you'll give.
402453 </ p >
403454
404455 < div className = "p-3 rounded-lg bg-muted/50 border text-sm" >
@@ -421,7 +472,7 @@ export function VerifiedFeedbackDialog({ agentMint, agentName, children, onSucce
421472 Processing...
422473 </ >
423474 ) : (
424- "Pay & Get Signature ($0.01 )"
475+ "Pay & Get Signature ($0.001 )"
425476 ) }
426477 </ Button >
427478
0 commit comments