11import {
22 Dialog ,
3+ DialogContent ,
4+ DialogFooter ,
35 DialogHeader ,
46 DialogTitle ,
5- DialogFooter ,
6- DialogContent ,
77} from "@/components/ui/dialog" ;
8- import { OnTransactionFunc } from "@frames.js/render" ;
8+ import { OnSignatureFunc , OnTransactionFunc } from "@frames.js/render" ;
99import type {
1010 ComposerActionFormResponse ,
1111 ComposerActionState ,
1212} from "frames.js/types" ;
1313import { useCallback , useEffect , useRef } from "react" ;
14+ import { Abi , TypedDataDomain } from "viem" ;
1415import { z } from "zod" ;
1516
16- const miniappMessageSchema = z . object ( {
17- type : z . string ( ) ,
18- data : z . record ( z . unknown ( ) ) ,
17+ const composerFormCreateCastMessageSchema = z . object ( {
18+ type : z . literal ( "createCast" ) ,
19+ data : z . object ( {
20+ cast : z . object ( {
21+ parent : z . string ( ) . optional ( ) ,
22+ text : z . string ( ) ,
23+ embeds : z . array ( z . string ( ) . min ( 1 ) . url ( ) ) . min ( 1 ) ,
24+ } ) ,
25+ } ) ,
26+ } ) ;
27+
28+ const ethSendTransactionActionSchema = z . object ( {
29+ chainId : z . string ( ) ,
30+ method : z . literal ( "eth_sendTransaction" ) ,
31+ attribution : z . boolean ( ) . optional ( ) ,
32+ params : z . object ( {
33+ abi : z . custom < Abi > ( ) ,
34+ to : z . custom < `0x${string } `> (
35+ ( val ) : val is `0x${string } ` =>
36+ typeof val === "string" && val . startsWith ( "0x" )
37+ ) ,
38+ value : z . string ( ) . optional ( ) ,
39+ data : z
40+ . custom < `0x${string } `> (
41+ ( val ) : val is `0x${string } ` =>
42+ typeof val === "string" && val . startsWith ( "0x" )
43+ )
44+ . optional ( ) ,
45+ } ) ,
46+ } ) ;
47+
48+ const ethSignTypedDataV4ActionSchema = z . object ( {
49+ chainId : z . string ( ) ,
50+ method : z . literal ( "eth_signTypedData_v4" ) ,
51+ params : z . object ( {
52+ domain : z . custom < TypedDataDomain > ( ) ,
53+ types : z . unknown ( ) ,
54+ primaryType : z . string ( ) ,
55+ message : z . record ( z . unknown ( ) ) ,
56+ } ) ,
57+ } ) ;
58+
59+ const transactionRequestBodySchema = z . object ( {
60+ requestId : z . string ( ) ,
61+ tx : z . union ( [ ethSendTransactionActionSchema , ethSignTypedDataV4ActionSchema ] ) ,
1962} ) ;
2063
64+ const composerActionMessageSchema = z . discriminatedUnion ( "type" , [
65+ composerFormCreateCastMessageSchema ,
66+ z . object ( {
67+ type : z . literal ( "requestTransaction" ) ,
68+ data : transactionRequestBodySchema ,
69+ } ) ,
70+ ] ) ;
71+
2172type ComposerFormActionDialogProps = {
2273 composerActionForm : ComposerActionFormResponse ;
2374 onClose : ( ) => void ;
2475 onSave : ( arg : { composerState : ComposerActionState } ) => void ;
2576 onTransaction ?: OnTransactionFunc ;
77+ onSignature ?: OnSignatureFunc ;
78+ // TODO: Consider moving this into return value of onTransaction
79+ connectedAddress ?: `0x${string } `;
2680} ;
2781
2882export function ComposerFormActionDialog ( {
2983 composerActionForm,
3084 onClose,
3185 onSave,
3286 onTransaction,
87+ onSignature,
88+ connectedAddress,
3389} : ComposerFormActionDialogProps ) {
3490 const onSaveRef = useRef ( onSave ) ;
3591 onSaveRef . current = onSave ;
@@ -50,52 +106,91 @@ export function ComposerFormActionDialog({
50106
51107 useEffect ( ( ) => {
52108 const handleMessage = ( event : MessageEvent ) => {
53- const result = miniappMessageSchema . parse ( event . data ) ;
54-
55- if ( result ?. type === "requestTransaction" ) {
56- onTransaction ?.( {
57- transactionData : result . data . tx as any ,
58- } ) . then ( ( txHash ) => {
59- if ( txHash ) {
60- postMessageToIframe ( {
61- type : "transactionResponse" ,
62- data : {
63- requestId : result . data . requestId ,
64- success : true ,
65- receipt : {
66- // address: farcasterFrameConfig.connectedAddress,
67- transactionId : txHash ,
68- } ,
69- } ,
70- } ) ;
71- } else {
72- postMessageToIframe ( {
73- type : "transactionResponse" ,
74- data : {
75- requestId : result . data . requestId ,
76- success : false ,
77- message : "User rejected the request" ,
78- } ,
79- } ) ;
80- }
81- } ) ;
82- return ;
83- }
109+ const result = composerActionMessageSchema . safeParse ( event . data ) ;
84110
85111 // on error is not called here because there can be different messages that don't have anything to do with composer form actions
86112 // instead we are just waiting for the correct message
87113 if ( ! result . success ) {
88- console . warn ( "Invalid message received" , event . data ) ;
114+ console . warn ( "Invalid message received" , event . data , result . error ) ;
89115 return ;
90116 }
91117
92- if ( result . data . data . cast . embeds . length > 2 ) {
93- console . warn ( "Only first 2 embeds are shown in the cast" ) ;
94- }
118+ const message = result . data ;
119+
120+ if ( message . type === "requestTransaction" ) {
121+ if ( message . data . tx . method === "eth_sendTransaction" ) {
122+ onTransaction ?.( {
123+ transactionData : message . data . tx ,
124+ } ) . then ( ( txHash ) => {
125+ if ( txHash ) {
126+ postMessageToIframe ( {
127+ type : "transactionResponse" ,
128+ data : {
129+ requestId : message . data . requestId ,
130+ success : true ,
131+ receipt : {
132+ address : connectedAddress ,
133+ transactionId : txHash ,
134+ } ,
135+ } ,
136+ } ) ;
137+ } else {
138+ postMessageToIframe ( {
139+ type : "transactionResponse" ,
140+ data : {
141+ requestId : message . data . requestId ,
142+ success : false ,
143+ message : "User rejected the request" ,
144+ } ,
145+ } ) ;
146+ }
147+ } ) ;
148+ } else if ( message . data . tx . method === "eth_signTypedData_v4" ) {
149+ onSignature ?.( {
150+ signatureData : {
151+ chainId : message . data . tx . chainId ,
152+ method : "eth_signTypedData_v4" ,
153+ params : {
154+ domain : message . data . tx . params . domain ,
155+ types : message . data . tx . params . types as any ,
156+ primaryType : message . data . tx . params . primaryType ,
157+ message : message . data . tx . params . message ,
158+ } ,
159+ } ,
160+ } ) . then ( ( signature ) => {
161+ if ( signature ) {
162+ postMessageToIframe ( {
163+ type : "signatureResponse" ,
164+ data : {
165+ requestId : message . data . requestId ,
166+ success : true ,
167+ receipt : {
168+ address : connectedAddress ,
169+ transactionId : signature ,
170+ } ,
171+ } ,
172+ } ) ;
173+ } else {
174+ postMessageToIframe ( {
175+ type : "signatureResponse" ,
176+ data : {
177+ requestId : message . data . requestId ,
178+ success : false ,
179+ message : "User rejected the request" ,
180+ } ,
181+ } ) ;
182+ }
183+ } ) ;
184+ }
185+ } else if ( message . type === "createCast" ) {
186+ if ( message . data . cast . embeds . length > 2 ) {
187+ console . warn ( "Only first 2 embeds are shown in the cast" ) ;
188+ }
95189
96- onSaveRef . current ( {
97- composerState : result . data . data . cast ,
98- } ) ;
190+ onSaveRef . current ( {
191+ composerState : message . data . cast ,
192+ } ) ;
193+ }
99194 } ;
100195
101196 window . addEventListener ( "message" , handleMessage ) ;
0 commit comments