From 766b1059a8e24f3d65c7493f105d2c3a73124410 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Sun, 20 Apr 2025 20:25:29 +1200 Subject: [PATCH 01/10] [Dashboard] Add webhook version support to Universal Bridge --- .../webhooks/components/webhooks.client.tsx | 23 ++-- .../src/components/pay/PayConfig.tsx | 126 +++++++++++------- .../settings/ApiKeys/validations.ts | 7 + 3 files changed, 95 insertions(+), 61 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx index 357c7d508e2..d32f21624c5 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx @@ -57,6 +57,7 @@ type Webhook = { createdAt: string; id: string; secret: string; + version?: string; // TODO (UB) make this mandatory after migration }; type PayWebhooksPageProps = { @@ -75,7 +76,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) { queryFn: async () => { const res = await payServerProxy({ method: "GET", - pathname: "/webhooks/get-all", + pathname: "/webhooks/get-all", // TODO (UB) switch to UB endpoint after migration searchParams: { /** * @deprecated - remove after migration @@ -147,6 +148,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) { Url Secret Created + Webhook Version Delete @@ -166,6 +168,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) { {formatDistanceToNow(webhook.createdAt, { addSuffix: true })} + {webhook.version || "v1"} ) { Create Webhook - Receive a webhook notification when a pay event occurs. + Receive a webhook notification when a bridge, swap or onramp + event occurs. @@ -294,21 +298,20 @@ function CreateWebhookButton(props: PropsWithChildren) { )} /> - {/* Note: this is a "fake" form field since there is nothing to select yet */} - Event Type - - + - - Purchase Complete - + v2 + v1 - Which event should trigger this webhook? + Select the data format of the webhook payload (v2 recommended, + v1 for legacy users). diff --git a/apps/dashboard/src/components/pay/PayConfig.tsx b/apps/dashboard/src/components/pay/PayConfig.tsx index 069f3caca7e..507e32fae87 100644 --- a/apps/dashboard/src/components/pay/PayConfig.tsx +++ b/apps/dashboard/src/components/pay/PayConfig.tsx @@ -42,6 +42,7 @@ export const PayConfig: React.FC = (props) => { resolver: zodResolver(apiKeyPayConfigValidationSchema), values: { payoutAddress: payService?.payoutAddress ?? "", + developerFeeBPS: payService?.developerFeeBPS ?? 0, }, }); @@ -59,49 +60,55 @@ export const PayConfig: React.FC = (props) => { }, }); - const handleSubmit = form.handleSubmit(({ payoutAddress }) => { - const services = props.project.services; + const handleSubmit = form.handleSubmit( + ({ payoutAddress, developerFeeBPS }) => { + const services = props.project.services; - const newServices = services.map((service) => { - if (service.name !== "pay") { - return service; - } + const newServices = services.map((service) => { + if (service.name !== "pay") { + return service; + } - return { - ...service, - payoutAddress, - }; - }); + return { + ...service, + payoutAddress, + developerFeeBPS: developerFeeBPS ? developerFeeBPS * 100 : 0, + }; + }); - updateProject.mutate( - { - services: newServices, - }, - { - onSuccess: () => { - toast.success("Fee sharing updated"); - trackEvent({ - category: TRACKING_CATEGORY, - action: "configuration-update", - label: "success", - data: { - payoutAddress, - }, - }); + updateProject.mutate( + { + services: newServices, }, - onError: (err) => { - toast.error("Failed to update fee sharing"); - console.error(err); - trackEvent({ - category: TRACKING_CATEGORY, - action: "configuration-update", - label: "error", - error: err, - }); + { + onSuccess: () => { + toast.success("Fee sharing updated"); + trackEvent({ + category: TRACKING_CATEGORY, + action: "configuration-update", + label: "success", + data: { + payoutAddress, + }, + }); + }, + onError: (err) => { + toast.error("Failed to update fee sharing"); + console.error(err); + trackEvent({ + category: TRACKING_CATEGORY, + action: "configuration-update", + label: "error", + error: err, + }); + }, }, - }, - ); - }); + ); + }, + (errors) => { + console.log(errors); + }, + ); if (!payService) { return ( @@ -140,7 +147,7 @@ export const PayConfig: React.FC = (props) => {

thirdweb collects a 0.3% protocol fee on swap transactions. You - may set your own developer fee in addition to this fee. + may set your own developer fee in addition to this fee.{" "} = (props) => {

- ( - - Recipient address - - - - - )} - /> +
+ ( + + Recipient address + + + + + )} + /> + ( + + Fee amount + +
+ + % +
+
+
+ )} + /> +
diff --git a/apps/dashboard/src/components/settings/ApiKeys/validations.ts b/apps/dashboard/src/components/settings/ApiKeys/validations.ts index 49b524180cc..9ce385b6e3e 100644 --- a/apps/dashboard/src/components/settings/ApiKeys/validations.ts +++ b/apps/dashboard/src/components/settings/ApiKeys/validations.ts @@ -114,6 +114,13 @@ export const apiKeyEmbeddedWalletsValidationSchema = z.object({ export const apiKeyPayConfigValidationSchema = z.object({ payoutAddress: payoutAddressValidation, + developerFeeBPS: z + .string() + .transform((val) => Number(val)) + .refine((val) => val >= 0 && val <= 100, { + message: "Developer fee must be between 0 and 100", + }) + .optional(), }); export type ApiKeyEmbeddedWalletsValidationSchema = z.infer< From cad2457221efa88dd4824dcd48d57d2429c77008 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Sun, 20 Apr 2025 20:25:29 +1200 Subject: [PATCH 02/10] [Dashboard] Add webhook version support to Universal Bridge --- .../webhooks/components/webhooks.client.tsx | 23 ++-- .../src/components/pay/PayConfig.tsx | 126 +++++++++++------- .../settings/ApiKeys/validations.ts | 7 + 3 files changed, 95 insertions(+), 61 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx index 357c7d508e2..d32f21624c5 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx @@ -57,6 +57,7 @@ type Webhook = { createdAt: string; id: string; secret: string; + version?: string; // TODO (UB) make this mandatory after migration }; type PayWebhooksPageProps = { @@ -75,7 +76,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) { queryFn: async () => { const res = await payServerProxy({ method: "GET", - pathname: "/webhooks/get-all", + pathname: "/webhooks/get-all", // TODO (UB) switch to UB endpoint after migration searchParams: { /** * @deprecated - remove after migration @@ -147,6 +148,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) { Url Secret Created + Webhook Version Delete @@ -166,6 +168,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) { {formatDistanceToNow(webhook.createdAt, { addSuffix: true })} + {webhook.version || "v1"} ) { Create Webhook - Receive a webhook notification when a pay event occurs. + Receive a webhook notification when a bridge, swap or onramp + event occurs. @@ -294,21 +298,20 @@ function CreateWebhookButton(props: PropsWithChildren) { )} /> - {/* Note: this is a "fake" form field since there is nothing to select yet */} - Event Type - - + - - Purchase Complete - + v2 + v1 - Which event should trigger this webhook? + Select the data format of the webhook payload (v2 recommended, + v1 for legacy users). diff --git a/apps/dashboard/src/components/pay/PayConfig.tsx b/apps/dashboard/src/components/pay/PayConfig.tsx index 069f3caca7e..507e32fae87 100644 --- a/apps/dashboard/src/components/pay/PayConfig.tsx +++ b/apps/dashboard/src/components/pay/PayConfig.tsx @@ -42,6 +42,7 @@ export const PayConfig: React.FC = (props) => { resolver: zodResolver(apiKeyPayConfigValidationSchema), values: { payoutAddress: payService?.payoutAddress ?? "", + developerFeeBPS: payService?.developerFeeBPS ?? 0, }, }); @@ -59,49 +60,55 @@ export const PayConfig: React.FC = (props) => { }, }); - const handleSubmit = form.handleSubmit(({ payoutAddress }) => { - const services = props.project.services; + const handleSubmit = form.handleSubmit( + ({ payoutAddress, developerFeeBPS }) => { + const services = props.project.services; - const newServices = services.map((service) => { - if (service.name !== "pay") { - return service; - } + const newServices = services.map((service) => { + if (service.name !== "pay") { + return service; + } - return { - ...service, - payoutAddress, - }; - }); + return { + ...service, + payoutAddress, + developerFeeBPS: developerFeeBPS ? developerFeeBPS * 100 : 0, + }; + }); - updateProject.mutate( - { - services: newServices, - }, - { - onSuccess: () => { - toast.success("Fee sharing updated"); - trackEvent({ - category: TRACKING_CATEGORY, - action: "configuration-update", - label: "success", - data: { - payoutAddress, - }, - }); + updateProject.mutate( + { + services: newServices, }, - onError: (err) => { - toast.error("Failed to update fee sharing"); - console.error(err); - trackEvent({ - category: TRACKING_CATEGORY, - action: "configuration-update", - label: "error", - error: err, - }); + { + onSuccess: () => { + toast.success("Fee sharing updated"); + trackEvent({ + category: TRACKING_CATEGORY, + action: "configuration-update", + label: "success", + data: { + payoutAddress, + }, + }); + }, + onError: (err) => { + toast.error("Failed to update fee sharing"); + console.error(err); + trackEvent({ + category: TRACKING_CATEGORY, + action: "configuration-update", + label: "error", + error: err, + }); + }, }, - }, - ); - }); + ); + }, + (errors) => { + console.log(errors); + }, + ); if (!payService) { return ( @@ -140,7 +147,7 @@ export const PayConfig: React.FC = (props) => {

thirdweb collects a 0.3% protocol fee on swap transactions. You - may set your own developer fee in addition to this fee. + may set your own developer fee in addition to this fee.{" "} = (props) => {

- ( - - Recipient address - - - - - )} - /> +
+ ( + + Recipient address + + + + + )} + /> + ( + + Fee amount + +
+ + % +
+
+
+ )} + /> +
diff --git a/apps/dashboard/src/components/settings/ApiKeys/validations.ts b/apps/dashboard/src/components/settings/ApiKeys/validations.ts index 49b524180cc..9ce385b6e3e 100644 --- a/apps/dashboard/src/components/settings/ApiKeys/validations.ts +++ b/apps/dashboard/src/components/settings/ApiKeys/validations.ts @@ -114,6 +114,13 @@ export const apiKeyEmbeddedWalletsValidationSchema = z.object({ export const apiKeyPayConfigValidationSchema = z.object({ payoutAddress: payoutAddressValidation, + developerFeeBPS: z + .string() + .transform((val) => Number(val)) + .refine((val) => val >= 0 && val <= 100, { + message: "Developer fee must be between 0 and 100", + }) + .optional(), }); export type ApiKeyEmbeddedWalletsValidationSchema = z.infer< From 35977be9946c438ee41cf60a9f6d876efa42ed19 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Mon, 21 Apr 2025 14:16:13 -0700 Subject: [PATCH 03/10] feat: update webhook urls for UB --- .../webhooks/components/webhooks.client.tsx | 73 +++++-------------- 1 file changed, 18 insertions(+), 55 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx index d32f21624c5..a8c9206b160 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx @@ -42,6 +42,7 @@ import { } from "@/components/ui/table"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { getAuthToken } from "app/api/lib/getAuthToken"; import { formatDistanceToNow } from "date-fns"; import { PlusIcon, TrashIcon } from "lucide-react"; import { type PropsWithChildren, useState } from "react"; @@ -61,33 +62,22 @@ type Webhook = { }; type PayWebhooksPageProps = { - /** - * @deprecated - remove after migration - */ clientId: string; - // switching to projectId for lookup, but have to send both during migration - projectId: string; - teamId: string; }; +const UB_BASE_URL = process.env.NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST; + export function PayWebhooksPage(props: PayWebhooksPageProps) { const webhooksQuery = useQuery({ queryKey: ["webhooks", props.clientId], queryFn: async () => { const res = await payServerProxy({ method: "GET", - pathname: "/webhooks/get-all", // TODO (UB) switch to UB endpoint after migration - searchParams: { - /** - * @deprecated - remove after migration - */ - clientId: props.clientId, - // switching to projectId for lookup, but have to send both during migration - projectId: props.projectId, - teamId: props.teamId, - }, + pathname: `${UB_BASE_URL}/v1/developer/webhooks`, headers: { "Content-Type": "application/json", + "x-client-id-override": props.clientId, + Authorization: `Bearer ${getAuthToken()}`, }, }); @@ -108,11 +98,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) { return (

No webhooks configured yet.

- +