Skip to content

Commit b032d00

Browse files
authored
fix: update miniapp transactions spec implementation (#518)
1 parent d13c6ca commit b032d00

File tree

4 files changed

+111
-78
lines changed

4 files changed

+111
-78
lines changed

.changeset/real-crabs-heal.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"template-next-starter-with-examples": patch
3+
"@frames.js/debugger": patch
4+
---
5+
6+
fix: update miniapp transactions spec implementation

packages/debugger/app/components/composer-form-action-dialog.tsx

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { useCallback, useEffect, useRef } from "react";
1414
import { Abi, TypedDataDomain } from "viem";
1515
import { z } from "zod";
1616

17-
const composerFormCreateCastMessageSchema = z.object({
17+
const createCastRequestSchemaLegacy = z.object({
1818
type: z.literal("createCast"),
1919
data: z.object({
2020
cast: z.object({
@@ -56,17 +56,35 @@ const ethSignTypedDataV4ActionSchema = z.object({
5656
}),
5757
});
5858

59-
const transactionRequestBodySchema = z.object({
60-
requestId: z.string(),
61-
tx: z.union([ethSendTransactionActionSchema, ethSignTypedDataV4ActionSchema]),
59+
const walletActionRequestSchema = z.object({
60+
jsonrpc: z.literal("2.0"),
61+
id: z.string(),
62+
method: z.literal("fc_requestWalletAction"),
63+
params: z.object({
64+
action: z.union([
65+
ethSendTransactionActionSchema,
66+
ethSignTypedDataV4ActionSchema,
67+
]),
68+
}),
6269
});
6370

64-
const composerActionMessageSchema = z.discriminatedUnion("type", [
65-
composerFormCreateCastMessageSchema,
66-
z.object({
67-
type: z.literal("requestTransaction"),
68-
data: transactionRequestBodySchema,
71+
const createCastRequestSchema = z.object({
72+
jsonrpc: z.literal("2.0"),
73+
id: z.union([z.string(), z.number(), z.null()]),
74+
method: z.literal("fc_createCast"),
75+
params: z.object({
76+
cast: z.object({
77+
parent: z.string().optional(),
78+
text: z.string(),
79+
embeds: z.array(z.string().min(1).url()).min(1),
80+
}),
6981
}),
82+
});
83+
84+
const composerActionMessageSchema = z.union([
85+
createCastRequestSchemaLegacy,
86+
walletActionRequestSchema,
87+
createCastRequestSchema,
7088
]);
7189

7290
type ComposerFormActionDialogProps = {
@@ -106,6 +124,10 @@ export function ComposerFormActionDialog({
106124

107125
useEffect(() => {
108126
const handleMessage = (event: MessageEvent) => {
127+
if (event.origin !== new URL(composerActionForm.url).origin) {
128+
return;
129+
}
130+
109131
const result = composerActionMessageSchema.safeParse(event.data);
110132

111133
// on error is not called here because there can be different messages that don't have anything to do with composer form actions
@@ -117,78 +139,77 @@ export function ComposerFormActionDialog({
117139

118140
const message = result.data;
119141

120-
if (message.type === "requestTransaction") {
121-
if (message.data.tx.method === "eth_sendTransaction") {
142+
if ("type" in message) {
143+
// Handle legacy messages
144+
onSaveRef.current({
145+
composerState: message.data.cast,
146+
});
147+
} else if (message.method === "fc_requestWalletAction") {
148+
if (message.params.action.method === "eth_sendTransaction") {
122149
onTransaction?.({
123-
transactionData: message.data.tx,
150+
transactionData: message.params.action,
124151
}).then((txHash) => {
125152
if (txHash) {
126153
postMessageToIframe({
127-
type: "transactionResponse",
128-
data: {
129-
requestId: message.data.requestId,
130-
success: true,
131-
receipt: {
132-
address: connectedAddress,
133-
transactionId: txHash,
134-
},
154+
jsonrpc: "2.0",
155+
id: message.id,
156+
result: {
157+
address: connectedAddress,
158+
transactionId: txHash,
135159
},
136160
});
137161
} else {
138162
postMessageToIframe({
139-
type: "transactionResponse",
140-
data: {
141-
requestId: message.data.requestId,
142-
success: false,
163+
jsonrpc: "2.0",
164+
id: message.id,
165+
error: {
166+
code: -32000,
143167
message: "User rejected the request",
144168
},
145169
});
146170
}
147171
});
148-
} else if (message.data.tx.method === "eth_signTypedData_v4") {
172+
} else if (message.params.action.method === "eth_signTypedData_v4") {
149173
onSignature?.({
150174
signatureData: {
151-
chainId: message.data.tx.chainId,
152-
method: "eth_signTypedData_v4",
175+
chainId: message.params.action.chainId,
176+
method: message.params.action.method,
153177
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,
178+
domain: message.params.action.params.domain,
179+
types: message.params.action.params.types as any,
180+
primaryType: message.params.action.params.primaryType,
181+
message: message.params.action.params.message,
158182
},
159183
},
160184
}).then((signature) => {
161185
if (signature) {
162186
postMessageToIframe({
163-
type: "signatureResponse",
164-
data: {
165-
requestId: message.data.requestId,
166-
success: true,
167-
receipt: {
168-
address: connectedAddress,
169-
transactionId: signature,
170-
},
187+
jsonrpc: "2.0",
188+
id: message.id,
189+
result: {
190+
address: connectedAddress,
191+
transactionId: signature,
171192
},
172193
});
173194
} else {
174195
postMessageToIframe({
175-
type: "signatureResponse",
176-
data: {
177-
requestId: message.data.requestId,
178-
success: false,
196+
jsonrpc: "2.0",
197+
id: message.id,
198+
error: {
199+
code: -32000,
179200
message: "User rejected the request",
180201
},
181202
});
182203
}
183204
});
184205
}
185-
} else if (message.type === "createCast") {
186-
if (message.data.cast.embeds.length > 2) {
206+
} else if (message.method === "fc_createCast") {
207+
if (message.params.cast.embeds.length > 2) {
187208
console.warn("Only first 2 embeds are shown in the cast");
188209
}
189210

190211
onSaveRef.current({
191-
composerState: message.data.cast,
212+
composerState: message.params.cast,
192213
});
193214
}
194215
};

packages/debugger/app/frames/route.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,16 @@ const composerActionFormParser = z.object({
2323
title: z.string().min(1),
2424
});
2525

26-
const jsonResponseParser = z.preprocess(
27-
(data) => {
28-
if (typeof data === "object" && data !== null && !("type" in data)) {
29-
return {
30-
type: "message",
31-
...data,
32-
};
33-
}
26+
const jsonResponseParser = z.preprocess((data) => {
27+
if (typeof data === "object" && data !== null && !("type" in data)) {
28+
return {
29+
type: "message",
30+
...data,
31+
};
32+
}
3433

35-
return data;
36-
},
37-
z.discriminatedUnion("type", [
38-
castActionFrameParser,
39-
castActionMessageParser,
40-
composerActionFormParser,
41-
])
42-
);
34+
return data;
35+
}, z.discriminatedUnion("type", [castActionFrameParser, castActionMessageParser, composerActionFormParser]));
4336

4437
const errorResponseParser = z.object({
4538
message: z.string().min(1),
@@ -184,7 +177,14 @@ export async function POST(req: NextRequest): Promise<Response> {
184177
);
185178
}
186179

187-
return r.clone();
180+
const headers = new Headers(r.headers);
181+
// Proxied requests could have content-encoding set, which breaks the response
182+
headers.delete("content-encoding");
183+
return new Response(r.body, {
184+
headers,
185+
status: r.status,
186+
statusText: r.statusText,
187+
});
188188
}
189189

190190
if (isPostRedirect && r.status !== 302) {
@@ -211,7 +211,14 @@ export async function POST(req: NextRequest): Promise<Response> {
211211
throw new Error("Invalid frame response");
212212
}
213213

214-
return r.clone();
214+
const headers = new Headers(r.headers);
215+
// Proxied requests could have content-encoding set, which breaks the response
216+
headers.delete("content-encoding");
217+
return new Response(r.body, {
218+
headers,
219+
status: r.status,
220+
statusText: r.statusText,
221+
});
215222
}
216223

217224
const htmlString = await r.text();

templates/next-starter-with-examples/app/examples/transaction-miniapp/miniapp/page.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ export default function MiniappPage({
4444
// Handle form submission here
4545
windowObject?.parent.postMessage(
4646
{
47-
type: "requestTransaction",
48-
data: {
49-
requestId: uuidv4(),
50-
tx: {
47+
jsonrpc: "2.0",
48+
id: uuidv4(),
49+
method: "fc_requestWalletAction",
50+
params: {
51+
action: {
5152
chainId: "eip155:10",
5253
method: "eth_sendTransaction",
5354
params: {
@@ -67,10 +68,11 @@ export default function MiniappPage({
6768
const handleRequestSignature = useCallback(() => {
6869
windowObject?.parent.postMessage(
6970
{
70-
type: "requestTransaction",
71-
data: {
72-
requestId: uuidv4(),
73-
tx: {
71+
jsonrpc: "2.0",
72+
id: uuidv4(),
73+
method: "fc_requestWalletAction",
74+
params: {
75+
action: {
7476
chainId: "eip155:10", // OP Mainnet 10
7577
method: "eth_signTypedData_v4",
7678
params: {
@@ -152,13 +154,10 @@ export default function MiniappPage({
152154
Request Signature
153155
</button>
154156
</div>
155-
{message?.data?.success ? (
156-
<div>
157-
Transaction sent successfully: {message?.data?.receipt?.transactionId}{" "}
158-
sent from {message?.data?.receipt?.address}
159-
</div>
157+
{message?.result ? (
158+
<pre>{JSON.stringify(message?.result, null, 2)}</pre>
160159
) : (
161-
<div className="text-red-500">{message?.data?.message}</div>
160+
<div className="text-red-500">{message?.result?.message}</div>
162161
)}
163162
</div>
164163
);

0 commit comments

Comments
 (0)