Skip to content

Commit a1e0aef

Browse files
add send transaction button
1 parent 76a5035 commit a1e0aef

File tree

5 files changed

+209
-14
lines changed

5 files changed

+209
-14
lines changed

apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import CreateAccessToken from "./create-access-token";
1+
import CreateAccessToken from "./create-access-token.client";
22

33
export function KeyManagement({
44
maskedAdminKey,
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"use client";
2+
import { Spinner } from "@/components/ui/Spinner/Spinner";
3+
import {} from "@/components/ui/alert";
4+
import { Button } from "@/components/ui/button";
5+
import {
6+
Dialog,
7+
DialogContent,
8+
DialogHeader,
9+
DialogTitle,
10+
} from "@/components/ui/dialog";
11+
import { Input } from "@/components/ui/input";
12+
import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env";
13+
import { useMutation } from "@tanstack/react-query";
14+
import { Loader2 } from "lucide-react";
15+
import { useState } from "react";
16+
import { toast } from "sonner";
17+
import { WalletAddress } from "../../../../../../../@/components/blocks/wallet-address";
18+
import { CodeClient } from "../../../../../../../@/components/ui/code/code.client";
19+
import type { Wallet } from "../wallet-table/types";
20+
21+
export default function SendDummyTx(props: {
22+
authToken: string;
23+
wallet: Wallet;
24+
}) {
25+
const [modalOpen, setModalOpen] = useState(false);
26+
const [accessToken, setAccessToken] = useState("");
27+
const sendDummyTxMutation = useMutation({
28+
mutationFn: async (args: {
29+
walletAddress: string;
30+
accessToken: string;
31+
}) => {
32+
const response = await fetch(
33+
`${THIRDWEB_ENGINE_CLOUD_URL}/account/send-transaction`,
34+
{
35+
method: "POST",
36+
headers: {
37+
"Content-Type": "application/json",
38+
Authorization: `Bearer ${props.authToken}`,
39+
},
40+
body: JSON.stringify({
41+
executionOptions: {
42+
type: "AA",
43+
signerAddress: args.walletAddress,
44+
},
45+
transactionParams: [
46+
{
47+
to: "0xeb0effdfb4dc5b3d5d3ac6ce29f3ed213e95d675",
48+
value: "0",
49+
},
50+
],
51+
vaultAccessToken: args.accessToken,
52+
chainId: "84532",
53+
}),
54+
},
55+
);
56+
return response.json();
57+
},
58+
onError: (error) => {
59+
toast.error(error.message);
60+
},
61+
});
62+
63+
const handleCloseModal = () => {
64+
setModalOpen(false);
65+
};
66+
67+
const isLoading = sendDummyTxMutation.isPending;
68+
69+
return (
70+
<>
71+
<Button
72+
variant={"primary"}
73+
onClick={() => setModalOpen(true)}
74+
disabled={isLoading}
75+
className="flex flex-row items-center gap-2"
76+
>
77+
{isLoading && <Loader2 className="animate-spin" />}
78+
Send Test Transaction
79+
</Button>
80+
81+
<Dialog open={modalOpen} onOpenChange={handleCloseModal} modal={true}>
82+
<DialogContent className="overflow-hidden p-0">
83+
<DialogHeader className="p-6">
84+
<DialogTitle>Send Test Transaction</DialogTitle>
85+
</DialogHeader>
86+
{sendDummyTxMutation.isPending ? (
87+
<div className="flex flex-col items-center justify-center gap-4 p-10">
88+
<Spinner className="size-8" />
89+
<p className="text-muted-foreground text-sm">
90+
Sending transaction...
91+
</p>
92+
</div>
93+
) : sendDummyTxMutation.data ? (
94+
<div>
95+
<div className="space-y-6 p-6 pt-0">
96+
<div className="space-y-4">
97+
<div>
98+
<h3 className="mb-2 font-medium text-sm">
99+
Transaction Status
100+
</h3>
101+
<p className="text-muted-foreground text-sm">
102+
{sendDummyTxMutation.data.status}
103+
</p>
104+
<h3 className="mb-2 font-medium text-sm">
105+
Transaction Result
106+
</h3>
107+
<div className="flex flex-col gap-2">
108+
<CodeClient
109+
lang="json"
110+
className="bg-background"
111+
code={JSON.stringify(sendDummyTxMutation.data, null, 2)}
112+
/>
113+
</div>
114+
</div>
115+
</div>
116+
</div>
117+
118+
<div className="flex justify-end gap-3 border-t bg-card px-6 py-4">
119+
<Button onClick={handleCloseModal} variant={"primary"}>
120+
Close
121+
</Button>
122+
</div>
123+
</div>
124+
) : (
125+
<div className="px-6 pb-6">
126+
<div className="flex flex-col gap-4">
127+
<div className="flex flex-col items-start gap-2">
128+
<h3 className="mb-2 font-medium text-md">Sending from</h3>
129+
<WalletAddress
130+
address={props.wallet.address}
131+
className="pointer-events-none"
132+
/>
133+
</div>
134+
<p className="text-sm text-warning-text">
135+
This action requries a wallet access token.
136+
</p>
137+
<Input
138+
type="password"
139+
placeholder="Enter your wallet access token"
140+
value={accessToken}
141+
onChange={(e) => setAccessToken(e.target.value)}
142+
/>
143+
<div className="flex justify-end gap-3">
144+
<Button
145+
variant={"outline"}
146+
onClick={() => {
147+
setAccessToken("");
148+
setModalOpen(false);
149+
}}
150+
>
151+
Cancel
152+
</Button>
153+
<Button
154+
variant={"primary"}
155+
onClick={() =>
156+
sendDummyTxMutation.mutate({
157+
walletAddress: props.wallet.address,
158+
accessToken,
159+
})
160+
}
161+
disabled={!accessToken || sendDummyTxMutation.isPending}
162+
>
163+
{sendDummyTxMutation.isPending ? (
164+
<>
165+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
166+
Sending...
167+
</>
168+
) : (
169+
"Send Transaction"
170+
)}
171+
</Button>
172+
</div>
173+
</div>
174+
</div>
175+
)}
176+
</DialogContent>
177+
</Dialog>
178+
</>
179+
);
180+
}

apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Button } from "@/components/ui/button";
22
import { CodeServer } from "../../../../../../../@/components/ui/code/code.server";
33
import { THIRDWEB_ENGINE_CLOUD_URL } from "../../../../../../../@/constants/env";
4+
import type { Wallet } from "../wallet-table/types";
5+
import SendDummyTx from "./send-dummy-tx.client";
46

5-
export function TryItOut() {
7+
export function TryItOut(props: { authToken: string; wallet?: Wallet }) {
68
return (
79
<div className="flex flex-col gap-6 overflow-hidden rounded-lg border border-border bg-card p-6">
810
<div className="flex flex-row items-center gap-4">
@@ -16,40 +18,46 @@ export function TryItOut() {
1618
</p>
1719
</div>
1820
</div>
19-
<Button variant={"secondary"}>View API reference</Button>
2021
</div>
2122
<div>
2223
<CodeServer
2324
lang="ts"
24-
code={typescriptCodeExample()}
25+
code={sendTransactionExample()}
2526
className="bg-background"
2627
/>
28+
<div className="h-4" />
29+
<div className="flex flex-row justify-end gap-4">
30+
<Button variant={"secondary"}>View API reference</Button>
31+
{props.wallet && (
32+
<SendDummyTx authToken={props.authToken} wallet={props.wallet} />
33+
)}
34+
</div>
2735
</div>
2836
</div>
2937
);
3038
}
3139

32-
const typescriptCodeExample = () => `\
40+
const sendTransactionExample = () => `\
3341
const response = fetch(
3442
"${THIRDWEB_ENGINE_CLOUD_URL}/account/send-transaction",
3543
{
3644
method: "POST",
3745
headers: {
3846
"Content-Type": "application/json",
39-
"x-secret-key": <your-project-secret-key>,
47+
"x-secret-key": "<your-project-secret-key>",
4048
},
4149
body: JSON.stringify({
4250
"executionOptions": {
4351
"type": "AA",
44-
"signerAddress": <your-server-wallet-address>
52+
"signerAddress": "<your-server-wallet-address>"
4553
},
4654
"transactionParams": [
4755
{
4856
"to": "0xeb0effdfb4dc5b3d5d3ac6ce29f3ed213e95d675",
4957
"value": "0"
5058
}
5159
],
52-
"vaultAccessToken": <your-wallet-access-token>,
60+
"vaultAccessToken": "<your-wallet-access-token>",
5361
"chainId": "84532"
5462
}),
5563
}

apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { getProject } from "@/api/projects";
22
import { THIRDWEB_VAULT_URL } from "@/constants/env";
33
import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk";
4+
import { notFound } from "next/navigation";
5+
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
46
import { KeyManagement } from "./components/key-management";
57
import { TryItOut } from "./components/try-it-out";
68
import type { Wallet } from "./wallet-table/types";
@@ -15,9 +17,16 @@ export default async function TransactionsServerWalletsPage(props: {
1517

1618
const { team_slug, project_slug } = await props.params;
1719

18-
const project = await getProject(team_slug, project_slug);
20+
const [authToken, project] = await Promise.all([
21+
getAuthToken(),
22+
getProject(team_slug, project_slug),
23+
]);
1924

20-
const projectEngineCloudService = project?.services.find(
25+
if (!project || !authToken) {
26+
notFound();
27+
}
28+
29+
const projectEngineCloudService = project.services.find(
2130
(service) => service.name === "engineCloud",
2231
);
2332

@@ -37,9 +46,7 @@ export default async function TransactionsServerWalletsPage(props: {
3746
})
3847
: { data: { items: [] }, error: null, success: true };
3948

40-
if (!project) {
41-
return <div>Error: Project not found</div>;
42-
}
49+
const wallet = eoas.data?.items[0] as Wallet | undefined;
4350

4451
return (
4552
<>
@@ -58,7 +65,7 @@ export default async function TransactionsServerWalletsPage(props: {
5865
projectId={project.id}
5966
teamId={project.teamId}
6067
/>
61-
<TryItOut />
68+
<TryItOut authToken={authToken} wallet={wallet} />
6269
</div>
6370
)}
6471
</>

0 commit comments

Comments
 (0)