Skip to content

Commit

Permalink
feat: improve send transaction form (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xbulma committed Nov 21, 2023
1 parent 22faa9a commit fc7b235
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 106 deletions.
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

0 comments on commit fc7b235

Please sign in to comment.