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

feat: improve send transaction form #43

Merged
merged 1 commit into from
Nov 20, 2023
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
1 change: 1 addition & 0 deletions front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions front/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 0 additions & 5 deletions front/src/app/api/users/save/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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) });
}
2 changes: 1 addition & 1 deletion front/src/components/Balance/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
67 changes: 38 additions & 29 deletions front/src/components/History/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Text style={{ marginTop: "2rem", marginBottom: "4rem" }}>
Fetching latest transactions...
</Text>
);

return (
<Flex direction="column" style={{ marginTop: "2rem", marginBottom: "4rem" }}>
<Text size="6" weight="bold">
History
</Text>
<Flex direction="row" gap="2" justify="between">
<Text size="6" weight="bold">
History
</Text>
</Flex>
<Separator my="3" size="4" color="gray" />

<Flex direction="column" gap="2">
{!loading &&
Array.isArray(txs) &&
txs
.sort((a: Log, b: Log) => {
return Number(b.blockNumber) - Number(a.blockNumber);
})
.map((tx: Log) => {
return (
<Box key={tx?.blockNumber}>
<Flex direction="row" gap="1" justify="between">
<Text size="2">
<Link
href={`https://goerli.basescan.org/tx/${tx.transactionHash}`}
target="_blank"
>
{tx?.transactionHash?.toString().slice(0, 4)}...
{tx?.transactionHash?.toString().slice(-4)}
</Link>
</Text>
<Text size="1">
{(tx as unknown as { args: { status: boolean } }).args.status}
</Text>
txs.map((tx: Log) => {
return (
<Box key={tx?.blockNumber}>
<Flex direction="row" gap="1" justify="between">
<Text size="2">
<Link
href={`https://goerli.basescan.org/tx/${tx.transactionHash}`}
target="_blank"
>
{tx?.transactionHash?.toString().slice(0, 4)}...
{tx?.transactionHash?.toString().slice(-4)}
</Link>
</Text>
<Text size="1">
{(tx as unknown as { args: { status: boolean } }).args.status}
</Text>
{(tx as unknown as { args: { success: boolean } })?.args.success ? (
<Badge color="green">Complete</Badge>
</Flex>
</Box>
);
})}
) : (
<Badge color="red">Failed</Badge>
)}
</Flex>
</Box>
);
})}
</Flex>
</Flex>
);
Expand Down
190 changes: 131 additions & 59 deletions front/src/components/SendTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -26,37 +32,51 @@ const builder = new UserOpBuilder(smartWallet.client.chain as Chain);
export function SendTransaction() {
const [txReceipt, setTxReceipt] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<any>(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&currencies=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 (
Expand All @@ -67,7 +87,7 @@ export function SendTransaction() {
</Heading>
)}
{!txReceipt && !isLoading && (
<form
<Form.Root
style={{
display: "flex",
flexDirection: "column",
Expand All @@ -78,34 +98,69 @@ export function SendTransaction() {
}}
onSubmit={async (e) => await submitTx(e)}
>
<Flex direction="column">
<Flex direction="column" gap="2">
<TextField.Input
<Flex direction="column" gap="2">
<Flex direction="column" gap="3">
<Form.Field
name="address"
placeholder="address"
style={{ paddingInline: "0.5rem" }}
/>
<Flex direction="row" gap="2" width="100%">
<TextFieldRoot>
<TextFieldInput
name="value"
placeholder="value ($)"
style={{ paddingInline: "0.5rem" }}
/>
<TextFieldSlot>
<Text weight="bold" style={{ alignSelf: "center" }}>
USD
style={{
paddingInline: "0.5rem",
}}
>
<Flex direction="column" gap="1">
<Flex direction="row" justify="between" gap="2">
<Form.Label>To</Form.Label>
<Form.Message match="valueMissing" style={{ color: "var(--accent-9)" }}>
Please enter a recipient address!
</Form.Message>
<Form.Message match="patternMismatch" style={{ color: "var(--accent-9)" }}>
Please provide a valid address!
</Form.Message>
</Flex>
<Form.Control asChild>
<TextFieldInput required pattern="^0x[a-fA-F0-9]{40}$" placeholder="0x..." />
</Form.Control>
</Flex>
</Form.Field>
<Form.Field name="amount" defaultValue={0} style={{ paddingInline: "0.5rem" }}>
<Flex direction="column" gap="1">
<Flex direction="row" justify="between" gap="2">
<Form.Label>Amount (USD)</Form.Label>
<Form.Message match="valueMissing" style={{ color: "var(--accent-9)" }}>
Please enter a value!
</Form.Message>
<Form.Message match="typeMismatch" style={{ color: "var(--accent-9)" }}>
Please provide a valid value!
</Form.Message>
<Form.Message match="rangeOverflow" style={{ color: "var(--accent-9)" }}>
Insufficient balance!
</Form.Message>
</Flex>
<Flex direction="column" gap="2">
<Form.Control asChild>
<TextFieldInput
required
placeholder="10"
type="number"
min={0}
max={balance?.toString() || "0"}
/>
</Form.Control>
<Text size="2" weight="bold" style={{ paddingInline: "0.5rem" }}>
${balance.toString().slice(0, 4)} available
</Text>
</TextFieldSlot>
</TextFieldRoot>
</Flex>
<Text size="2" weight="bold" style={{ paddingInline: "0.5rem" }}>
${balance.toString().slice(0, 4)} available
</Text>
</Flex>
</Flex>
</Form.Field>
</Flex>
{error && (
<Text size="2" weight="bold" style={{ color: "var(--accent-9)" }}>
{error}
</Text>
)}
</Flex>

<Button type="submit">Send</Button>
</form>
</Form.Root>
)}

{isLoading && (
Expand All @@ -120,18 +175,35 @@ export function SendTransaction() {
{txReceipt && !isLoading && (
<>
<Flex direction="column" justify="center" align="center" grow="1" gap="5">
<CheckCircledIcon height="50" width="100%" color="var(--teal-11)" />
<Link
// href={`https://goerli.basescan.org/tx/${txReceipt.receipt.transactionHash}`}
href={`https://goerli.basescan.org/tx/${"0xfc67bb936e6637388ec01e3f2889615b21ed6cf67a58a82265255435546d4d36"}`}
target="_blank"
style={{ textDecoration: "none" }}
>
<Flex direction="row" gap="2" style={{ color: "var(--teal-11)" }}>
See transaction
<ExternalLinkIcon style={{ alignSelf: "center", color: "var(--teal-11)" }} />
</Flex>
</Link>
{txReceipt.success ? (
<>
<CheckCircledIcon height="50" width="100%" color="var(--teal-11)" />
<Link
href={`https://goerli.basescan.org/tx/${txReceipt.receipt.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
<Flex direction="row" gap="2" style={{ color: "var(--teal-11)" }}>
See transaction
<ExternalLinkIcon style={{ alignSelf: "center", color: "var(--teal-11)" }} />
</Flex>
</Link>
</>
) : (
<>
<CrossCircledIcon height="50" width="100%" color="var(--red-9)" />
<Link
href={`https://goerli.basescan.org/tx/${txReceipt.receipt.transactionHash}`}
target="_blank"
style={{ textDecoration: "none" }}
>
<Flex direction="row" gap="2" style={{ color: "var(--red-9)" }}>
Transaction failed!
<ExternalLinkIcon style={{ alignSelf: "center", color: "var(--red-9)" }} />
</Flex>
</Link>
</>
)}
</Flex>
</>
)}
Expand Down
4 changes: 1 addition & 3 deletions front/src/libs/smart-wallet/hook/useSmartWalletHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ export function useSmartWalletHook() {

smartWallet.client.watchEvent({
address: address,
onLogs: (logs: any) => {
console.log("logs", logs);
},
onLogs: (logs: any) => {},
});
}, [address]);

Expand Down
Loading