-
Notifications
You must be signed in to change notification settings - Fork 619
[Dashboard] Add x402 fee configuration #8434
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@thirdweb-dev/service-utils": patch | ||
| --- | ||
|
|
||
| Update engineCloud service config |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import type { Project } from "@/api/project/projects"; | ||
|
|
||
| type X402Fee = { | ||
| feeRecipient: string; | ||
| feeBps: number; | ||
| }; | ||
|
|
||
| /** | ||
| * Extract x402 fee configuration from project's engineCloud service | ||
| */ | ||
| export function getX402Fees(project: Project): X402Fee { | ||
| const engineCloudService = project.services.find( | ||
| (service) => service.name === "engineCloud", | ||
| ); | ||
|
|
||
| if (!engineCloudService) { | ||
| return { | ||
| feeRecipient: "", | ||
| feeBps: 0, | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| feeRecipient: engineCloudService.x402FeeRecipient || "", | ||
| feeBps: engineCloudService.x402FeeBPS || 0, | ||
| }; | ||
| } | ||
joaquim-verges marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,181 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { zodResolver } from "@hookform/resolvers/zod"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useMutation } from "@tanstack/react-query"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useForm } from "react-hook-form"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from "sonner"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Project } from "@/api/project/projects"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { SettingsCard } from "@/components/blocks/SettingsCard"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Button } from "@/components/ui/button"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Form, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FormControl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FormField, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FormItem, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FormLabel, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@/components/ui/form"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Input } from "@/components/ui/input"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { updateProjectClient } from "@/hooks/useApi"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type ApiKeyPayConfigValidationSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| apiKeyPayConfigValidationSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@/schema/validations"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface X402FeeConfigProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| project: Project; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fees: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| feeRecipient: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| feeBps: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectWalletAddress?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const X402FeeConfig: React.FC<X402FeeConfigProps> = (props) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const form = useForm<ApiKeyPayConfigValidationSchema>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolver: zodResolver(apiKeyPayConfigValidationSchema), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| values: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| developerFeeBPS: props.fees.feeBps ? props.fees.feeBps / 100 : 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payoutAddress: props.fees.feeRecipient ?? "", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updateFeeMutation = useMutation({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mutationFn: async (values: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payoutAddress: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| developerFeeBPS: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Find and update the engineCloud service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newServices = props.project.services.map((service) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (service.name === "engineCloud") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...service, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| x402FeeBPS: values.developerFeeBPS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| x402FeeRecipient: values.payoutAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Update the project with the new services configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await updateProjectClient( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectId: props.project.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teamId: props.project.teamId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| services: newServices, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleSubmit = form.handleSubmit( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ({ payoutAddress, developerFeeBPS }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateFeeMutation.mutate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| developerFeeBPS: developerFeeBPS ? developerFeeBPS * 100 : 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payoutAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onError: (err) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error("Failed to update fee configuration"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onSuccess: () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.success("Fee configuration updated"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (errors) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(errors); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Form {...form}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <form autoComplete="off" onSubmit={handleSubmit}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <SettingsCard | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bottomText="Fees are sent to recipient address" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorText={form.getFieldState("payoutAddress").error?.message} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| noPermissionText={undefined} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| saveButton={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled: !form.formState.isDirty, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isPending: updateFeeMutation.isPending, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "submit", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h3 className="font-semibold text-xl tracking-tight"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Fee Sharing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </h3> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="mt-1.5 mb-4 text-foreground text-sm"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| thirdweb collects a 0.3% service fee on x402 transactions. You may | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set your own developer fee in addition to this fee. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="grid grid-cols-1 gap-4 lg:grid-cols-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormField | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| control={form.control} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name="payoutAddress" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| render={({ field }) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormLabel>Recipient address</FormLabel> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormControl> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-2 sm:flex-row"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...field} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="sm:flex-1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="0x..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {props.projectWalletAddress && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!props.projectWalletAddress) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| form.setValue( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "payoutAddress", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| props.projectWalletAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shouldDirty: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shouldTouch: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shouldValidate: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size="sm" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Use Project Wallet | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormControl> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormField | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| control={form.control} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name="developerFeeBPS" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| render={({ field }) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormLabel>Fee amount</FormLabel> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormControl> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Input {...field} placeholder="0.5" type="number" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-muted-foreground text-sm">%</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormControl> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+160
to
+174
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Display validation errors for the fee amount field. The Add error display for the fee field: <FormField
control={form.control}
name="developerFeeBPS"
render={({ field }) => (
<FormItem>
<FormLabel>Fee amount</FormLabel>
<FormControl>
<div className="flex items-center gap-2">
<Input {...field} placeholder="0.5" type="number" />
<span className="text-muted-foreground text-sm">%</span>
</div>
</FormControl>
+ <FormMessage />
</FormItem>
)}
/>Note: You'll need to import 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </SettingsCard> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,46 @@ | ||
| import { redirect } from "next/navigation"; | ||
| import { getAuthToken } from "@/api/auth-token"; | ||
| import { getProject } from "@/api/project/projects"; | ||
| import { getTeamBySlug } from "@/api/team/get-team"; | ||
| import { getX402Fees } from "@/api/x402/config"; | ||
| import { getProjectWallet } from "@/lib/server/project-wallet"; | ||
| import { loginRedirect } from "@/utils/redirects"; | ||
| import { X402FeeConfig } from "./X402FeeConfig"; | ||
|
|
||
| export default async function Page(props: { | ||
| params: Promise<{ team_slug: string; project_slug: string }>; | ||
| }) { | ||
| const params = await props.params; | ||
| const { team_slug, project_slug } = await props.params; | ||
|
|
||
| const [team, project, authToken] = await Promise.all([ | ||
| getTeamBySlug(team_slug), | ||
| getProject(team_slug, project_slug), | ||
| getAuthToken(), | ||
| ]); | ||
|
|
||
| const authToken = await getAuthToken(); | ||
| if (!authToken) { | ||
| loginRedirect( | ||
| `/team/${params.team_slug}/${params.project_slug}/x402/configuration`, | ||
| ); | ||
| loginRedirect(`/team/${team_slug}/${project_slug}/x402/configuration`); | ||
| } | ||
|
|
||
| if (!team) { | ||
| redirect("/team"); | ||
| } | ||
|
|
||
| const project = await getProject(params.team_slug, params.project_slug); | ||
| if (!project) { | ||
| redirect(`/team/${params.team_slug}`); | ||
| redirect(`/team/${team_slug}`); | ||
| } | ||
|
|
||
| const projectWallet = await getProjectWallet(project); | ||
|
|
||
| const fees = getX402Fees(project); | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-6"> | ||
| <div className="rounded-lg border border-border bg-card p-8 text-center"> | ||
| <h2 className="text-2xl font-semibold">Coming Soon</h2> | ||
| <p className="mt-2 text-muted-foreground"> | ||
| x402 payments configuration will be available soon. | ||
| </p> | ||
| </div> | ||
| <X402FeeConfig | ||
| fees={fees} | ||
| project={project} | ||
| projectWalletAddress={projectWallet?.address} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Export the
X402Feetype for external consumers.The
X402Feetype is referenced by components likeX402FeeConfig.tsx(line 26-29) which define their own inline version of this type. Exporting it would enable type reuse and ensure consistency across the codebase.Apply this diff:
📝 Committable suggestion
🤖 Prompt for AI Agents