Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update miniapp transactions spec implementation #518

Merged
merged 1 commit into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/real-crabs-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"template-next-starter-with-examples": patch
"@frames.js/debugger": patch
---

fix: update miniapp transactions spec implementation
113 changes: 67 additions & 46 deletions packages/debugger/app/components/composer-form-action-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useCallback, useEffect, useRef } from "react";
import { Abi, TypedDataDomain } from "viem";
import { z } from "zod";

const composerFormCreateCastMessageSchema = z.object({
const createCastRequestSchemaLegacy = z.object({
type: z.literal("createCast"),
data: z.object({
cast: z.object({
Expand Down Expand Up @@ -56,17 +56,35 @@ const ethSignTypedDataV4ActionSchema = z.object({
}),
});

const transactionRequestBodySchema = z.object({
requestId: z.string(),
tx: z.union([ethSendTransactionActionSchema, ethSignTypedDataV4ActionSchema]),
const walletActionRequestSchema = z.object({
jsonrpc: z.literal("2.0"),
id: z.string(),
method: z.literal("fc_requestWalletAction"),
params: z.object({
action: z.union([
ethSendTransactionActionSchema,
ethSignTypedDataV4ActionSchema,
]),
}),
});

const composerActionMessageSchema = z.discriminatedUnion("type", [
composerFormCreateCastMessageSchema,
z.object({
type: z.literal("requestTransaction"),
data: transactionRequestBodySchema,
const createCastRequestSchema = z.object({
jsonrpc: z.literal("2.0"),
id: z.union([z.string(), z.number(), z.null()]),
method: z.literal("fc_createCast"),
params: z.object({
cast: z.object({
parent: z.string().optional(),
text: z.string(),
embeds: z.array(z.string().min(1).url()).min(1),
}),
}),
});

const composerActionMessageSchema = z.union([
createCastRequestSchemaLegacy,
walletActionRequestSchema,
createCastRequestSchema,
]);

type ComposerFormActionDialogProps = {
Expand Down Expand Up @@ -106,6 +124,10 @@ export function ComposerFormActionDialog({

useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin !== new URL(composerActionForm.url).origin) {
return;
}

const result = composerActionMessageSchema.safeParse(event.data);

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

const message = result.data;

if (message.type === "requestTransaction") {
if (message.data.tx.method === "eth_sendTransaction") {
if ("type" in message) {
// Handle legacy messages
onSaveRef.current({
composerState: message.data.cast,
});
} else if (message.method === "fc_requestWalletAction") {
if (message.params.action.method === "eth_sendTransaction") {
onTransaction?.({
transactionData: message.data.tx,
transactionData: message.params.action,
}).then((txHash) => {
if (txHash) {
postMessageToIframe({
type: "transactionResponse",
data: {
requestId: message.data.requestId,
success: true,
receipt: {
address: connectedAddress,
transactionId: txHash,
},
jsonrpc: "2.0",
id: message.id,
result: {
address: connectedAddress,
transactionId: txHash,
},
});
} else {
postMessageToIframe({
type: "transactionResponse",
data: {
requestId: message.data.requestId,
success: false,
jsonrpc: "2.0",
id: message.id,
error: {
code: -32000,
message: "User rejected the request",
},
});
}
});
} else if (message.data.tx.method === "eth_signTypedData_v4") {
} else if (message.params.action.method === "eth_signTypedData_v4") {
onSignature?.({
signatureData: {
chainId: message.data.tx.chainId,
method: "eth_signTypedData_v4",
chainId: message.params.action.chainId,
method: message.params.action.method,
params: {
domain: message.data.tx.params.domain,
types: message.data.tx.params.types as any,
primaryType: message.data.tx.params.primaryType,
message: message.data.tx.params.message,
domain: message.params.action.params.domain,
types: message.params.action.params.types as any,
primaryType: message.params.action.params.primaryType,
message: message.params.action.params.message,
},
},
}).then((signature) => {
if (signature) {
postMessageToIframe({
type: "signatureResponse",
data: {
requestId: message.data.requestId,
success: true,
receipt: {
address: connectedAddress,
transactionId: signature,
},
jsonrpc: "2.0",
id: message.id,
result: {
address: connectedAddress,
transactionId: signature,
},
});
} else {
postMessageToIframe({
type: "signatureResponse",
data: {
requestId: message.data.requestId,
success: false,
jsonrpc: "2.0",
id: message.id,
error: {
code: -32000,
message: "User rejected the request",
},
});
}
});
}
} else if (message.type === "createCast") {
if (message.data.cast.embeds.length > 2) {
} else if (message.method === "fc_createCast") {
if (message.params.cast.embeds.length > 2) {
console.warn("Only first 2 embeds are shown in the cast");
}

onSaveRef.current({
composerState: message.data.cast,
composerState: message.params.cast,
});
}
};
Expand Down
43 changes: 25 additions & 18 deletions packages/debugger/app/frames/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,16 @@ const composerActionFormParser = z.object({
title: z.string().min(1),
});

const jsonResponseParser = z.preprocess(
(data) => {
if (typeof data === "object" && data !== null && !("type" in data)) {
return {
type: "message",
...data,
};
}
const jsonResponseParser = z.preprocess((data) => {
if (typeof data === "object" && data !== null && !("type" in data)) {
return {
type: "message",
...data,
};
}

return data;
},
z.discriminatedUnion("type", [
castActionFrameParser,
castActionMessageParser,
composerActionFormParser,
])
);
return data;
}, z.discriminatedUnion("type", [castActionFrameParser, castActionMessageParser, composerActionFormParser]));

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

return r.clone();
const headers = new Headers(r.headers);
// Proxied requests could have content-encoding set, which breaks the response
headers.delete("content-encoding");
return new Response(r.body, {
headers,
status: r.status,
statusText: r.statusText,
});
}

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

return r.clone();
const headers = new Headers(r.headers);
// Proxied requests could have content-encoding set, which breaks the response
headers.delete("content-encoding");
return new Response(r.body, {
headers,
status: r.status,
statusText: r.statusText,
});
}

const htmlString = await r.text();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ export default function MiniappPage({
// Handle form submission here
windowObject?.parent.postMessage(
{
type: "requestTransaction",
data: {
requestId: uuidv4(),
tx: {
jsonrpc: "2.0",
id: uuidv4(),
method: "fc_requestWalletAction",
params: {
action: {
chainId: "eip155:10",
method: "eth_sendTransaction",
params: {
Expand All @@ -67,10 +68,11 @@ export default function MiniappPage({
const handleRequestSignature = useCallback(() => {
windowObject?.parent.postMessage(
{
type: "requestTransaction",
data: {
requestId: uuidv4(),
tx: {
jsonrpc: "2.0",
id: uuidv4(),
method: "fc_requestWalletAction",
params: {
action: {
chainId: "eip155:10", // OP Mainnet 10
method: "eth_signTypedData_v4",
params: {
Expand Down Expand Up @@ -152,13 +154,10 @@ export default function MiniappPage({
Request Signature
</button>
</div>
{message?.data?.success ? (
<div>
Transaction sent successfully: {message?.data?.receipt?.transactionId}{" "}
sent from {message?.data?.receipt?.address}
</div>
{message?.result ? (
<pre>{JSON.stringify(message?.result, null, 2)}</pre>
) : (
<div className="text-red-500">{message?.data?.message}</div>
<div className="text-red-500">{message?.result?.message}</div>
)}
</div>
);
Expand Down
Loading