diff --git a/front/package.json b/front/package.json
index 8ce38fa..63b4144 100644
--- a/front/package.json
+++ b/front/package.json
@@ -13,6 +13,7 @@
"@emotion/styled": "^11.11.0",
"@peculiar/asn1-ecc": "2.3.6",
"@peculiar/asn1-schema": "2.3.6",
+ "@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/themes": "^2.0.0",
"@rainbow-me/rainbowkit": "^1.0.0",
diff --git a/front/pnpm-lock.yaml b/front/pnpm-lock.yaml
index f8448f9..48bb795 100644
--- a/front/pnpm-lock.yaml
+++ b/front/pnpm-lock.yaml
@@ -17,6 +17,9 @@ dependencies:
'@peculiar/asn1-schema':
specifier: 2.3.6
version: 2.3.6
+ '@radix-ui/react-form':
+ specifier: ^0.0.3
+ version: 0.0.3(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-icons':
specifier: ^1.3.0
version: 1.3.0(react@18.2.0)
diff --git a/front/src/app/api/users/save/route.ts b/front/src/app/api/users/save/route.ts
index 6a9acb9..b960370 100644
--- a/front/src/app/api/users/save/route.ts
+++ b/front/src/app/api/users/save/route.ts
@@ -25,9 +25,6 @@ export async function POST(req: Request) {
args: [BigInt(id)],
});
- console.log("id", id);
- console.log("user", user);
-
if (user.account !== zeroAddress) {
return Response.json(undefined);
}
@@ -48,8 +45,6 @@ export async function POST(req: Request) {
args: [BigInt(id)],
});
- console.log("createdUser", createdUser);
-
await publicClient.waitForTransactionReceipt({ hash });
return Response.json({ ...createdUser, id: toHex(createdUser.id) });
}
diff --git a/front/src/components/Balance/index.tsx b/front/src/components/Balance/index.tsx
index 6ef1214..ea6b3d0 100644
--- a/front/src/components/Balance/index.tsx
+++ b/front/src/components/Balance/index.tsx
@@ -9,7 +9,7 @@ const css: CSSProperties = {
};
export default function Balance() {
- const { balance } = useBalance();
+ const { balance, refreshBalance } = useBalance();
let [intBalance, decimals] = balance.toFixed(2).split(".");
return (
diff --git a/front/src/components/History/index.tsx b/front/src/components/History/index.tsx
index a7331b4..8b27896 100644
--- a/front/src/components/History/index.tsx
+++ b/front/src/components/History/index.tsx
@@ -1,47 +1,56 @@
"use client";
import { useTransaction } from "@/providers/TransactionProvider";
-import { Badge, Box, Flex, Link, Separator, Text } from "@radix-ui/themes";
+import { Badge, Box, Button, Flex, Link, Separator, Text } from "@radix-ui/themes";
import { Log } from "viem";
export default function History() {
- const { loading, txs } = useTransaction();
+ const { loading, txs, getLastTxs, unwatchLogs } = useTransaction();
+
+ if (loading)
+ return (
+
+ Fetching latest transactions...
+
+ );
return (
-
- History
-
+
+
+ History
+
+
{!loading &&
Array.isArray(txs) &&
- txs
- .sort((a: Log, b: Log) => {
- return Number(b.blockNumber) - Number(a.blockNumber);
- })
- .map((tx: Log) => {
- return (
-
-
-
-
- {tx?.transactionHash?.toString().slice(0, 4)}...
- {tx?.transactionHash?.toString().slice(-4)}
-
-
-
- {(tx as unknown as { args: { status: boolean } }).args.status}
-
+ txs.map((tx: Log) => {
+ return (
+
+
+
+
+ {tx?.transactionHash?.toString().slice(0, 4)}...
+ {tx?.transactionHash?.toString().slice(-4)}
+
+
+
+ {(tx as unknown as { args: { status: boolean } }).args.status}
+
+ {(tx as unknown as { args: { success: boolean } })?.args.success ? (
Complete
-
-
- );
- })}
+ ) : (
+ Failed
+ )}
+
+
+ );
+ })}
);
diff --git a/front/src/components/SendTransaction.tsx b/front/src/components/SendTransaction.tsx
index 77e5f96..e6e0ef9 100644
--- a/front/src/components/SendTransaction.tsx
+++ b/front/src/components/SendTransaction.tsx
@@ -16,8 +16,14 @@ import {
} from "@radix-ui/themes";
import { UserOpBuilder, emptyHex } from "@/libs/smart-wallet/service/userOps";
import { useBalance } from "@/providers/BalanceProvider";
-import { CheckCircledIcon, CheckIcon, ExternalLinkIcon, ReloadIcon } from "@radix-ui/react-icons";
+import {
+ CheckCircledIcon,
+ CrossCircledIcon,
+ ExternalLinkIcon,
+ ReloadIcon,
+} from "@radix-ui/react-icons";
import { useMe } from "@/providers/MeProvider";
+import * as Form from "@radix-ui/react-form";
import Spinner from "./Spinner";
smartWallet.init();
@@ -26,37 +32,51 @@ const builder = new UserOpBuilder(smartWallet.client.chain as Chain);
export function SendTransaction() {
const [txReceipt, setTxReceipt] = useState(null);
const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
const { me } = useMe();
- const { balance } = useBalance();
+ const { balance, refreshBalance } = useBalance();
const submitTx = async (e: any) => {
setIsLoading(true);
+ setError(null);
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 { maxFeePerGas, maxPriorityFeePerGas }: EstimateFeesPerGasReturnType =
- await smartWallet.client.estimateFeesPerGas();
+ try {
+ const formData = new FormData(e.target as HTMLFormElement);
+ const address = formData?.get("address") as Hex;
+ const usdAmount = formData?.get("amount") as `${number}`;
+
+ const price: { ethereum: { usd: number } } = await (
+ await fetch("/api/price?ids=ethereum¤cies=usd")
+ ).json();
- const userOp = await builder.buildUserOp({
- calls: [
- {
- dest: address || "0x1878EA9134D500A3cEF3E89589ECA3656EECf48f",
- value: BigInt(value) || BigInt(11),
- data: emptyHex,
- },
- ],
- maxFeePerGas: maxFeePerGas as bigint,
- maxPriorityFeePerGas: maxPriorityFeePerGas as bigint,
- keyId: me?.keyId as Hex,
- });
+ const { maxFeePerGas, maxPriorityFeePerGas }: EstimateFeesPerGasReturnType =
+ await smartWallet.client.estimateFeesPerGas();
- const hash = await smartWallet.sendUserOperation({ userOp });
- const receipt = await smartWallet.waitForUserOperationReceipt({ hash });
- setTxReceipt(receipt);
+ const userOp = await builder.buildUserOp({
+ calls: [
+ {
+ dest: address.toLowerCase() as Hex,
+ value:
+ (BigInt(usdAmount) * BigInt(1e18)) / (BigInt(price.ethereum.usd * 100) / BigInt(100)), // 100 is the price precision
+ data: emptyHex,
+ },
+ ],
+ maxFeePerGas: maxFeePerGas as bigint,
+ maxPriorityFeePerGas: maxPriorityFeePerGas as bigint,
+ keyId: me?.keyId as Hex,
+ });
- setIsLoading(false);
+ const hash = await smartWallet.sendUserOperation({ userOp });
+ const receipt = await smartWallet.waitForUserOperationReceipt({ hash });
+ setTxReceipt(receipt);
+ } catch (e) {
+ console.error(e);
+ setError("Something went wrong!");
+ } finally {
+ setIsLoading(false);
+ refreshBalance();
+ }
};
return (
@@ -67,7 +87,7 @@ export function SendTransaction() {
)}
{!txReceipt && !isLoading && (
-
+
)}
{isLoading && (
@@ -120,18 +175,35 @@ export function SendTransaction() {
{txReceipt && !isLoading && (
<>
-
-
-
- See transaction
-
-
-
+ {txReceipt.success ? (
+ <>
+
+
+
+ See transaction
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+ Transaction failed!
+
+
+
+ >
+ )}
>
)}
diff --git a/front/src/libs/smart-wallet/hook/useSmartWalletHook.tsx b/front/src/libs/smart-wallet/hook/useSmartWalletHook.tsx
index e41c029..b005a0b 100644
--- a/front/src/libs/smart-wallet/hook/useSmartWalletHook.tsx
+++ b/front/src/libs/smart-wallet/hook/useSmartWalletHook.tsx
@@ -15,9 +15,7 @@ export function useSmartWalletHook() {
smartWallet.client.watchEvent({
address: address,
- onLogs: (logs: any) => {
- console.log("logs", logs);
- },
+ onLogs: (logs: any) => {},
});
}, [address]);
diff --git a/front/src/providers/BalanceProvider/index.tsx b/front/src/providers/BalanceProvider/index.tsx
index 38fcbb9..d45fafb 100644
--- a/front/src/providers/BalanceProvider/index.tsx
+++ b/front/src/providers/BalanceProvider/index.tsx
@@ -8,6 +8,7 @@ import { Hex } from "viem";
function useBalanceHook() {
// balance in usd
const [balance, setBalance] = useState(0);
+ const [increment, setIncrement] = useState(0);
const { me } = useMe();
const getBalance = useCallback(async (keyId: Hex) => {
@@ -18,14 +19,19 @@ function useBalanceHook() {
setBalance(ethBalance * price);
}, []);
+ const refreshBalance = useCallback(() => {
+ setIncrement((prev) => prev + 1);
+ }, []);
+
useEffect(() => {
if (!me?.keyId) return;
getBalance(me?.keyId);
- }, [me, getBalance]);
+ }, [me, getBalance, increment]);
return {
balance,
getBalance,
+ refreshBalance,
};
}
diff --git a/front/src/providers/TransactionProvider/index.tsx b/front/src/providers/TransactionProvider/index.tsx
index 7c1187c..abb3b10 100644
--- a/front/src/providers/TransactionProvider/index.tsx
+++ b/front/src/providers/TransactionProvider/index.tsx
@@ -1,8 +1,9 @@
"use client";
-import { useMe } from "@/providers/MeProvider";
+import { PUBLIC_CLIENT, ENTRYPOINT_ADDRESS } from "@/constants";
+import { Me, useMe } from "@/providers/MeProvider";
import { createContext, useCallback, useContext, useEffect, useState } from "react";
-import { GetLogsReturnType, Hex, Log } from "viem";
+import { Hex, Log } from "viem";
const useTxHook = () => {
const [txs, setTxs] = useState | null>(null);
@@ -14,21 +15,96 @@ const useTxHook = () => {
setLoading(true);
const res = await fetch(`/api/users/${keyId}/txs`);
const resJson = await res.json();
- setTxs(resJson.logs);
+ setTxs((prev) => {
+ const logs = resJson.logs.sort((a: Log, b: Log) => {
+ return Number(b.blockNumber) - Number(a.blockNumber);
+ });
+ if (!prev) return logs;
+ return [...prev, ...logs];
+ });
setLoading(false);
}, []);
- useEffect(() => {
- if (!me?.keyId) return;
- getLastTxs(me.keyId);
- }, [me?.keyId, getLastTxs]);
+ const unwatchLogs = PUBLIC_CLIENT.watchContractEvent({
+ address: ENTRYPOINT_ADDRESS,
+ abi: [
+ {
+ inputs: [
+ {
+ internalType: "bytes32",
+ name: "userOpHash",
+ type: "bytes32",
+ indexed: true,
+ },
+ {
+ internalType: "address",
+ name: "sender",
+ type: "address",
+ indexed: true,
+ },
+ {
+ internalType: "address",
+ name: "paymaster",
+ type: "address",
+ indexed: true,
+ },
+ {
+ internalType: "uint256",
+ name: "nonce",
+ type: "uint256",
+ indexed: false,
+ },
+ {
+ internalType: "bool",
+ name: "success",
+ type: "bool",
+ indexed: false,
+ },
+ {
+ internalType: "uint256",
+ name: "actualGasCost",
+ type: "uint256",
+ indexed: false,
+ },
+ {
+ internalType: "uint256",
+ name: "actualGasUsed",
+ type: "uint256",
+ indexed: false,
+ },
+ ],
+ type: "event",
+ name: "UserOperationEvent",
+ anonymous: false,
+ },
+ ],
+ eventName: "UserOperationEvent",
+ args: { sender: me?.account },
+ onLogs: (logs) => {
+ setLoading(true);
+ setTxs((prev) => {
+ if (!prev) return logs;
+ return [
+ ...prev,
+ ...logs.sort((a: Log, b: Log) => {
+ return Number(b.blockNumber) - Number(a.blockNumber);
+ }),
+ ];
+ });
+ setLoading(false);
+ },
+ });
- useEffect(() => {}, [txs]);
+ useEffect(() => {
+ if (!me) return;
+ getLastTxs(me?.keyId);
+ }, [me, getLastTxs]);
return {
loading,
txs,
getLastTxs,
+ unwatchLogs,
};
};