Skip to content

Commit 38b9eb7

Browse files
committed
sign console
1 parent 7fdfae8 commit 38b9eb7

21 files changed

+1703
-223
lines changed

app/admin-console.tsx

Lines changed: 15 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -1,220 +1,23 @@
1-
'use client'
2-
31
import React from 'react'
42

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'
444

45-
type FormData = z.infer<typeof formSchema>
5+
import { ExecuteConsole } from './execute-console'
6+
import { SignConsole } from './sign-console'
467

478
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-
929
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>
21922
)
22023
}

0 commit comments

Comments
 (0)