Skip to content

Commit

Permalink
feat: add tx receipt + base link + fix fees estimation (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
yum0e authored Nov 9, 2023
1 parent f5cd042 commit ad5fc2f
Show file tree
Hide file tree
Showing 16 changed files with 374 additions and 199 deletions.
23 changes: 18 additions & 5 deletions front/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@

html {
overflow-x: hidden;
background-color: #696E77;

background-color: #696e77;
}

body {
position: relative;
max-width: 390px;
min-height: 100svh;
margin: 0 auto;
margin: 0 auto;
overflow: hidden;

}

@media (min-width: 391px) {
Expand All @@ -25,6 +23,21 @@ body {
border-radius: 20px;
min-height: calc(100svh - 40px);
overflow: hidden;

}
}

@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

.spinner {
width: 60;
height: 60;
fill: rgb(52, 171, 52);
animation: spin 1s linear infinite;
}
3 changes: 1 addition & 2 deletions front/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "@radix-ui/themes/styles.css";
import "./globals.css";
import { Theme, ThemePanel } from "@radix-ui/themes";
import { Theme } from "@radix-ui/themes";
import ThemeProvider from "@/providers/ThemeProvider";
import { WalletConnectProvider } from "@/libs/wallet-connect";
import { SmartWalletProvider } from "@/libs/smart-wallet/SmartWalletProvider";
Expand All @@ -19,7 +19,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<ThemeProvider attribute="class">
<Theme>
<ModalProvider>{children}</ModalProvider>
{/* <ThemePanel /> */}
</Theme>
</ThemeProvider>
</WalletConnectProvider>
Expand Down
1 change: 1 addition & 0 deletions front/src/components/LoadingSpinner/Loader.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions front/src/components/LoadingSpinner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";
import LoaderSVG from "./Loader.svg";
import Image from "next/image";

export default function LoadingSpinner() {
return <Image src={LoaderSVG} className="spinner" alt="Loading..." />;
}
49 changes: 27 additions & 22 deletions front/src/components/SendTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,55 @@ import { UserOpBuilder } from "@/libs/smart-wallet/service/userOps";
import { baseGoerli } from "viem/chains";
import { SmartWalletProvider } from "@/libs/smart-wallet/SmartWalletProvider";
import { smartWallet } from "@/libs/smart-wallet";
import { useState } from "react";
import { Link } from "@radix-ui/themes";
import LoadingSpinner from "@/components/LoadingSpinner";

const builder = new UserOpBuilder(baseGoerli);
smartWallet.init();

export function SendTransaction() {
const { data, error, isLoading, isError, sendTransaction } = useSendTransaction();
const {
data: receipt,
isLoading: isPending,
isSuccess,
} = useWaitForTransaction({ hash: data?.hash });
const [txReceipt, setTxReceipt] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);

return (
<>
<form
onSubmit={async (e) => {
setIsLoading(true);
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const address = formData.get("address") as Hex;
const value = formData.get("value") as `${number}`;
const address = formData?.get("address") as Hex;
const value = formData?.get("value") as `${number}`;

const res = await smartWallet.client.sendUserOperation({
to: address ?? "0x1878EA9134D500A3cEF3E89589ECA3656EECf48f",
value: value ?? BigInt(11),
const hash = await smartWallet.sendUserOperation({
to: address || "0x1878EA9134D500A3cEF3E89589ECA3656EECf48f",
value: value || BigInt(11),
});

console.log("res", res);
console.log("hash", hash);
const receipt = await smartWallet.waitForUserOperationReceipt({ hash });
setTxReceipt(receipt);

console.log("receipt", receipt);
setIsLoading(false);
}}
>
<input name="address" placeholder="address" />
<input name="value" placeholder="value (ether)" />
<button type="submit">Send</button>
</form>

{isLoading && <div>Check wallet...</div>}
{isPending && <div>Transaction pending...</div>}
{isSuccess && (
<>
<div>Transaction Hash: {data?.hash}</div>
<div>
Transaction Receipt: <pre>{stringify(receipt, null, 2)}</pre>
</div>
</>
{isLoading && <LoadingSpinner />}

{txReceipt && !isLoading && (
<Link
href={`https://goerli.basescan.org/tx/${txReceipt.receipt.transactionHash}`}
target="_blank"
>
Tx Link
</Link>
)}
{isError && <div>Error: {error?.message}</div>}
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { SmartWalletClient } from "@/libs/smart-wallet/service/smart-wallet";

/* */
export type EstimateUserOperationGasReturnType = bigint;

export async function estimateUserOperationGas(
client: SmartWalletClient,
args: any,
): Promise<EstimateUserOperationGasReturnType> {
return await client.request({
method: "eth_estimateUserOperationGas" as any,
params: args,
});
}
13 changes: 13 additions & 0 deletions front/src/libs/smart-wallet/service/actions/getIsValidSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SmartWalletClient } from "@/libs/smart-wallet/service/smart-wallet";

export type GetIsValidSignatureReturnType = boolean;

export async function getIsValidSignature(
client: SmartWalletClient,
args: any,
): Promise<GetIsValidSignatureReturnType> {
return await client.request({
method: "eth_call" as any,
params: args,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { SmartWalletClient } from "@/libs/smart-wallet/service/smart-wallet";
import { Hash } from "viem";

export type GetUserOperationReceiptReturnType = Hash;

export async function getUserOperationReceipt(
client: SmartWalletClient,
args: { hash: Hash },
): Promise<any> {
return await client.request({
method: "eth_getUserOperationReceipt" as any,
params: [args.hash],
});
}
128 changes: 5 additions & 123 deletions front/src/libs/smart-wallet/service/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,123 +1,5 @@
import { UserOpBuilder } from "@/libs/smart-wallet/service/userOps";
import { P256Credential, WebAuthn } from "@/libs/webauthn";
import {
Chain,
Client,
Hash,
Hex,
RpcTransactionRequest,
encodeAbiParameters,
encodePacked,
} from "viem";

/* */
export type SendUserOperationReturnType = Hash;

export async function sendUserOperation(
client: Client,
args: { to: Hex; value: bigint },
): Promise<SendUserOperationReturnType> {
const builder = new UserOpBuilder(client.chain as Chain);

const { userOp, msgToSign } = await builder.buildUserOp({
to: args.to,
value: args.value,
});

const credentials: P256Credential = (await new WebAuthn().get(msgToSign)) as P256Credential;

const signatureUserOp = encodePacked(
["uint8", "uint48", "bytes"],
[
1,
0,
encodeAbiParameters(
[
{
type: "tuple",
name: "credentials",
components: [
{
name: "authenticatorData",
type: "bytes",
},
{
name: "clientDataJSON",
type: "string",
},
{
name: "challengeLocation",
type: "uint256",
},
{
name: "responseTypeLocation",
type: "uint256",
},
{
name: "r",
type: "bytes32",
},
{
name: "s",
type: "bytes32",
},
],
},
],
[
{
authenticatorData: credentials.authenticatorData,
clientDataJSON: JSON.stringify(credentials.clientData),
challengeLocation: BigInt(23),
responseTypeLocation: BigInt(1),
r: credentials.signature.r,
s: credentials.signature.s,
},
],
),
],
);

const userOpAsParams = builder.toParams({ ...userOp, signature: signatureUserOp });

return await client.request({
method: "eth_sendUserOperation" as any,
params: [userOpAsParams, builder.entryPoint],
});
}

/* */
export type EstimateUserOperationGasReturnType = bigint;

export async function estimateUserOperationGas(
client: Client,
args: any,
): Promise<EstimateUserOperationGasReturnType> {
return await client.request({
method: "eth_estimateUserOperationGas" as any,
params: args,
});
}

/* */
export type GetUserOperationReceiptReturnType = Hash;

export async function getUserOperationReceipt(client: Client, args: any): Promise<any> {
return await client.request({
method: "eth_getUserOperationReceipt" as any,
params: args,
});
}

/* */
export type GetIsValidSignatureReturnType = boolean;

export async function getIsValidSignature(
client: Client,
args: any,
): Promise<GetIsValidSignatureReturnType> {
return await client.request({
method: "eth_call" as any,
params: args,
});
}
export * from "./getUserOperationReceipt";
export * from "./sendUserOperation";
export * from "./estimateUserOperationGas";
export * from "./getIsValidSignature";
export * from "./waitForUserOperationReceipt";
27 changes: 27 additions & 0 deletions front/src/libs/smart-wallet/service/actions/sendUserOperation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SmartWalletClient } from "@/libs/smart-wallet/service/smart-wallet";
import { UserOpBuilder } from "@/libs/smart-wallet/service/userOps";
import { Hash, Hex, Chain, EstimateFeesPerGasReturnType } from "viem";

/* */
export type SendUserOperationReturnType = Hash;

export async function sendUserOperation(
client: SmartWalletClient,
args: { to: Hex; value: bigint },
): Promise<SendUserOperationReturnType> {
const builder = new UserOpBuilder(client.chain as Chain);

const gasPrice: EstimateFeesPerGasReturnType = await client.estimateFeesPerGas();

const userOp = await builder.buildUserOp({
to: args.to,
value: args.value,
maxFeePerGas: gasPrice.maxFeePerGas as bigint,
maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas as bigint,
});

return await client.request({
method: "eth_sendUserOperation" as any,
params: [builder.toParams(userOp), builder.entryPoint],
});
}
Loading

0 comments on commit ad5fc2f

Please sign in to comment.