|
1 | | -'use client' |
2 | | - |
3 | 1 | import React from 'react' |
4 | 2 |
|
5 | | -import { keepAbi } from '@/lib/keep' |
6 | | -import { zodResolver } from '@hookform/resolvers/zod' |
7 | | -import { Controller, useFieldArray, useForm } from 'react-hook-form' |
8 | | -import { toast } from 'sonner' |
9 | | -import { Address, Hex, isAddress, parseEther, parseSignature, zeroAddress } from 'viem' |
10 | | -import { useWriteContract } from 'wagmi' |
11 | | -import { z } from 'zod' |
12 | | - |
13 | | -enum Operation { |
14 | | - call, |
15 | | - delegatecall, |
16 | | - create, |
17 | | -} |
18 | | - |
19 | | -const signatureSchema = z.object({ |
20 | | - user: z |
21 | | - .string() |
22 | | - .min(1, 'User address is required') |
23 | | - .refine((val) => isAddress(val)), |
24 | | - signature: z.string().min(1, 'Signature is required'), |
25 | | -}) |
26 | | - |
27 | | -const formSchema = z.object({ |
28 | | - account: z |
29 | | - .string() |
30 | | - .min(1, 'Account is required') |
31 | | - .refine((val) => isAddress(val)) |
32 | | - .transform((val) => val as Address), |
33 | | - to: z |
34 | | - .string() |
35 | | - .min(1, 'To address is required') |
36 | | - .refine((val) => isAddress(val)) |
37 | | - .transform((val) => val as Address), |
38 | | - operation: z.nativeEnum(Operation), |
39 | | - value: z.string().min(1, 'Value is required'), |
40 | | - data: z.string().min(1, 'Data is required'), |
41 | | - signatures: z.array(signatureSchema), |
42 | | - chainId: z.number().int().positive('Chain ID must be a positive integer'), |
43 | | -}) |
| 3 | +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' |
44 | 4 |
|
45 | | -type FormData = z.infer<typeof formSchema> |
| 5 | +import { ExecuteConsole } from './execute-console' |
| 6 | +import { SignConsole } from './sign-console' |
46 | 7 |
|
47 | 8 | export const AdminConsole = () => { |
48 | | - const { control, handleSubmit } = useForm<FormData>({ |
49 | | - resolver: zodResolver(formSchema), |
50 | | - defaultValues: { |
51 | | - account: zeroAddress, |
52 | | - to: zeroAddress, |
53 | | - operation: Operation.call, |
54 | | - value: '', |
55 | | - data: '', |
56 | | - signatures: [{ user: '', signature: '' }], |
57 | | - chainId: 1, |
58 | | - }, |
59 | | - }) |
60 | | - const { fields, append, remove } = useFieldArray({ |
61 | | - control, |
62 | | - name: 'signatures', |
63 | | - }) |
64 | | - const { writeContractAsync } = useWriteContract() |
65 | | - |
66 | | - const onSubmit = async (data: FormData) => { |
67 | | - try { |
68 | | - const signatures = data.signatures.map((sig) => { |
69 | | - const { r, s, yParity } = parseSignature(sig.signature as Hex) |
70 | | - return { |
71 | | - user: sig.user as Address, |
72 | | - v: yParity === 0 ? 27 : 28, |
73 | | - r, |
74 | | - s, |
75 | | - } |
76 | | - }) |
77 | | - |
78 | | - const txHash = await writeContractAsync({ |
79 | | - address: data.account, |
80 | | - abi: keepAbi, |
81 | | - functionName: 'execute', |
82 | | - args: [data.operation, data.to, parseEther(data.value), data.data as Hex, signatures], |
83 | | - chainId: data.chainId, |
84 | | - }) |
85 | | - |
86 | | - toast.success(`Transaction sent: ${txHash}`) |
87 | | - } catch (error) { |
88 | | - console.error('Error:', error) |
89 | | - } |
90 | | - } |
91 | | - |
92 | 9 | return ( |
93 | | - <form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> |
94 | | - <div> |
95 | | - <label htmlFor="account" className="block text-sm font-medium text-gray-700"> |
96 | | - Account |
97 | | - </label> |
98 | | - <Controller |
99 | | - name="account" |
100 | | - control={control} |
101 | | - render={({ field }) => ( |
102 | | - <input {...field} type="text" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm" /> |
103 | | - )} |
104 | | - /> |
105 | | - </div> |
106 | | - <div> |
107 | | - <label htmlFor="to" className="block text-sm font-medium text-gray-700"> |
108 | | - To |
109 | | - </label> |
110 | | - <Controller |
111 | | - name="to" |
112 | | - control={control} |
113 | | - render={({ field }) => ( |
114 | | - <input {...field} type="text" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm" /> |
115 | | - )} |
116 | | - /> |
117 | | - </div> |
118 | | - <div> |
119 | | - <label htmlFor="operation" className="block text-sm font-medium text-gray-700"> |
120 | | - Operation |
121 | | - </label> |
122 | | - <Controller |
123 | | - name="operation" |
124 | | - control={control} |
125 | | - render={({ field }) => ( |
126 | | - <select {...field} className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm"> |
127 | | - {Object.values(Operation).map((op) => ( |
128 | | - <option key={op} value={op}> |
129 | | - {op} |
130 | | - </option> |
131 | | - ))} |
132 | | - </select> |
133 | | - )} |
134 | | - /> |
135 | | - </div> |
136 | | - <div> |
137 | | - <label htmlFor="value" className="block text-sm font-medium text-gray-700"> |
138 | | - Value (ETH) |
139 | | - </label> |
140 | | - <Controller |
141 | | - name="value" |
142 | | - control={control} |
143 | | - render={({ field }) => ( |
144 | | - <input |
145 | | - {...field} |
146 | | - type="number" |
147 | | - step="0.000000000000000001" |
148 | | - className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm" |
149 | | - /> |
150 | | - )} |
151 | | - /> |
152 | | - </div> |
153 | | - <div> |
154 | | - <label htmlFor="data" className="block text-sm font-medium text-gray-700"> |
155 | | - Data (bytes) |
156 | | - </label> |
157 | | - <Controller |
158 | | - name="data" |
159 | | - control={control} |
160 | | - render={({ field }) => ( |
161 | | - <textarea {...field} className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm" /> |
162 | | - )} |
163 | | - /> |
164 | | - </div> |
165 | | - <div> |
166 | | - <label className="block text-sm font-medium text-gray-700">Signatures</label> |
167 | | - {fields.map((field, index) => ( |
168 | | - <div key={field.id} className="flex space-x-2 mt-2"> |
169 | | - <Controller |
170 | | - name={`signatures.${index}.user`} |
171 | | - control={control} |
172 | | - render={({ field }) => ( |
173 | | - <input |
174 | | - {...field} |
175 | | - placeholder="User address" |
176 | | - className="flex-1 border border-gray-300 rounded-md shadow-sm" |
177 | | - /> |
178 | | - )} |
179 | | - /> |
180 | | - <Controller |
181 | | - name={`signatures.${index}.signature`} |
182 | | - control={control} |
183 | | - render={({ field }) => ( |
184 | | - <input |
185 | | - {...field} |
186 | | - placeholder="Signature" |
187 | | - className="flex-1 border border-gray-300 rounded-md shadow-sm" |
188 | | - /> |
189 | | - )} |
190 | | - /> |
191 | | - <button type="button" onClick={() => remove(index)} className="text-red-500"> |
192 | | - Remove |
193 | | - </button> |
194 | | - </div> |
195 | | - ))} |
196 | | - <button type="button" onClick={() => append({ user: '', signature: '' })} className="mt-2 text-blue-500"> |
197 | | - Add Signature |
198 | | - </button> |
199 | | - </div> |
200 | | - <div> |
201 | | - <label htmlFor="chainId" className="block text-sm font-medium text-gray-700"> |
202 | | - Chain ID |
203 | | - </label> |
204 | | - <Controller |
205 | | - name="chainId" |
206 | | - control={control} |
207 | | - render={({ field }) => ( |
208 | | - <input {...field} type="number" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm" /> |
209 | | - )} |
210 | | - /> |
211 | | - </div> |
212 | | - <button |
213 | | - type="submit" |
214 | | - className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" |
215 | | - > |
216 | | - Submit |
217 | | - </button> |
218 | | - </form> |
| 10 | + <Tabs defaultValue="sign" className="p-8 bg-zinc-100 border-2 border-white rounded-md w-[500px]"> |
| 11 | + <TabsList> |
| 12 | + <TabsTrigger value="sign">Sign</TabsTrigger> |
| 13 | + <TabsTrigger value="execute">Execute</TabsTrigger> |
| 14 | + </TabsList> |
| 15 | + <TabsContent value="sign"> |
| 16 | + <SignConsole /> |
| 17 | + </TabsContent> |
| 18 | + <TabsContent value="execute"> |
| 19 | + <ExecuteConsole /> |
| 20 | + </TabsContent> |
| 21 | + </Tabs> |
219 | 22 | ) |
220 | 23 | } |
0 commit comments