({
resolver: zodResolver(partnerFormSchema),
@@ -55,12 +58,15 @@ export function PartnerForm({
// Set the UI control properties based on existing data
accessControlEnabled: hasAccessControl,
serverVerifierEnabled: hasServerVerifier,
+ allowedOperationsEnabled: hasAllowedOperations,
},
+ mode: "onChange", // Validate on change for better user experience
});
// Watch the boolean flags for UI state
const accessControlEnabled = form.watch("accessControlEnabled");
const serverVerifierEnabled = form.watch("serverVerifierEnabled");
+ const allowedOperationsEnabled = form.watch("allowedOperationsEnabled");
// Setup field array for headers
const customHeaderFields = useFieldArray({
@@ -83,7 +89,10 @@ export function PartnerForm({
};
}
- // TODO add signature policies here
+ if (finalAccessControl && values.allowedOperationsEnabled) {
+ finalAccessControl.allowedOperations =
+ values.accessControl?.allowedOperations || [];
+ }
// if no values have been set, remove the accessControl object
if (
@@ -196,133 +205,158 @@ export function PartnerForm({
checked={accessControlEnabled}
onCheckedChange={(checked) => {
form.setValue("accessControlEnabled", checked);
- // If disabling access control, also disable server verifier
+ // If disabling access control, also disable server verifier and allowed operations
if (!checked) {
form.setValue("serverVerifierEnabled", false);
+ form.setValue("allowedOperationsEnabled", false);
}
}}
/>
{accessControlEnabled && (
-
-
-
-
- Server Verifier
-
-
- Configure a server verifier for access control
-
-
-
{
- form.setValue("serverVerifierEnabled", checked);
-
- // Initialize serverVerifier fields if enabling
- if (
- checked &&
- !form.getValues("accessControl.serverVerifier")
- ) {
- form.setValue("accessControl.serverVerifier", {
- url: "",
- headers: [],
- });
- }
- }}
- />
-
+ <>
+
+
+
+
+ Server Verifier
+
+
+ Configure a server verifier for access control
+
+
+
{
+ form.setValue("serverVerifierEnabled", checked);
- {serverVerifierEnabled && (
-
- (
-
- Server Verifier URL
-
-
-
-
- {form.formState.errors.accessControl?.serverVerifier
- ?.url?.message ||
- "Enter the URL of your server where verification requests will be sent"}
-
-
- )}
+ // Initialize serverVerifier fields if enabling
+ if (
+ checked &&
+ !form.getValues("accessControl.serverVerifier")
+ ) {
+ form.setValue("accessControl.serverVerifier", {
+ url: "",
+ headers: [],
+ });
+ }
+ }}
/>
+
-
-
Custom Headers
-
- {customHeaderFields.fields.map((field, headerIdx) => {
- return (
-
-
+ {serverVerifierEnabled && (
+
+ (
+
+ Server Verifier URL
+
- {
- customHeaderFields.remove(headerIdx);
- }}
- className="!w-auto px-3"
- type="button"
- >
-
-
-
- );
- })}
+
+
+ {form.formState.errors.accessControl?.serverVerifier
+ ?.url?.message ||
+ "Enter the URL of your server where verification requests will be sent"}
+
+
+ )}
+ />
-
{
- customHeaderFields.append({
- key: "",
- value: "",
- });
- }}
- type="button"
- >
-
- Add header
-
-
+
+
+ Custom Headers
+
+
+ {customHeaderFields.fields.map((field, headerIdx) => {
+ return (
+
+
+
+ {
+ customHeaderFields.remove(headerIdx);
+ }}
+ className="!w-auto px-3"
+ type="button"
+ >
+
+
+
+ );
+ })}
-
- Set custom headers to be sent along with verification
- requests
-
+
{
+ customHeaderFields.append({
+ key: "",
+ value: "",
+ });
+ }}
+ type="button"
+ >
+
+ Add header
+
+
+
+
+ Set custom headers to be sent along with verification
+ requests
+
+
-
- )}
-
+ )}
+
+
+ {/* Allowed Operations Section */}
+
{
+ form.setValue("allowedOperationsEnabled", checked);
+
+ // Initialize allowedOperations array if enabling
+ if (
+ checked &&
+ !form.getValues("accessControl.allowedOperations")
+ ) {
+ form.setValue("accessControl.allowedOperations", []);
+ }
+ }}
+ />
+ >
)}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/update-partner-form.client.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/update-partner-form.client.tsx
index 15027b09443..97540af1e2e 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/update-partner-form.client.tsx
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/update-partner-form.client.tsx
@@ -1,4 +1,6 @@
"use client";
+import { useDashboardRouter } from "@/lib/DashboardRouter";
+import { useParams } from "next/navigation";
import { toast } from "sonner";
import type { Ecosystem, Partner } from "../../../../../types";
import { useUpdatePartner } from "../../hooks/use-update-partner";
@@ -7,21 +9,30 @@ import { PartnerForm, type PartnerFormValues } from "./partner-form.client";
export function UpdatePartnerForm({
ecosystem,
partner,
- onSuccess,
authToken,
}: {
ecosystem: Ecosystem;
partner: Partner;
- onSuccess: () => void;
authToken: string;
}) {
+ const router = useDashboardRouter();
+ const params = useParams();
+ const teamSlug = params.team_slug as string;
+ const ecosystemSlug = params.slug as string;
+
const { mutateAsync: updatePartner, isPending } = useUpdatePartner(
{
authToken,
},
{
onSuccess: () => {
- onSuccess();
+ toast.success("Partner updated successfully", {
+ description: "The partner details have been updated.",
+ });
+
+ // Redirect to the redirect page that will take us back to the configuration page
+ const redirectPath = `/team/${teamSlug}/~/ecosystem/${ecosystemSlug}`;
+ router.push(redirectPath);
},
onError: (error) => {
const message =
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/update-partner-modal.client.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/update-partner-modal.client.tsx
deleted file mode 100644
index 71f4b7026ae..00000000000
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/update-partner-modal.client.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { useState } from "react";
-import type { Ecosystem, Partner } from "../../../../../types";
-import { UpdatePartnerForm } from "./update-partner-form.client";
-
-export function UpdatePartnerModal({
- children,
- ecosystem,
- partner,
- authToken,
-}: {
- children: React.ReactNode;
- ecosystem: Ecosystem;
- partner: Partner;
- authToken: string;
-}) {
- const [open, setOpen] = useState(false);
-
- return (
-
- {children}
-
-
- Update {partner.name}
-
- setOpen(false)}
- authToken={authToken}
- />
-
-
- );
-}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/ecosystem-partners-section.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/ecosystem-partners-section.tsx
index ec472671134..9a83f8d2034 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/ecosystem-partners-section.tsx
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/ecosystem-partners-section.tsx
@@ -3,9 +3,10 @@ import { AddPartnerDialogButton } from "../client/AddPartnerDialogButton";
import { PartnersTable } from "./partners-table";
export function EcosystemPartnersSection({
+ teamSlug,
ecosystem,
authToken,
-}: { ecosystem: Ecosystem; authToken: string }) {
+}: { teamSlug: string; ecosystem: Ecosystem; authToken: string }) {
return (
@@ -21,10 +22,18 @@ export function EcosystemPartnersSection({
own app.
-
+
-
+
);
}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/partners-table.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/partners-table.tsx
index d106ea802ac..d74d9d76e9a 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/partners-table.tsx
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/partners-table.tsx
@@ -12,20 +12,28 @@ import {
TableRow,
} from "@/components/ui/table";
import { ToolTipLabel } from "@/components/ui/tooltip";
+import { useDashboardRouter } from "@/lib/DashboardRouter";
import { cn } from "@/lib/utils";
import { Pencil, Trash2 } from "lucide-react";
-import Link from "next/link";
import { toast } from "sonner";
+import { Link } from "../../../../../../../../../../../tw-components/link";
import type { Ecosystem, Partner } from "../../../../../types";
import { usePartners } from "../../../hooks/use-partners";
import { useDeletePartner } from "../../hooks/use-delete-partner";
-import { UpdatePartnerModal } from "../client/update-partner-modal.client";
export function PartnersTable({
ecosystem,
authToken,
-}: { ecosystem: Ecosystem; authToken: string }) {
- const { partners, isPending } = usePartners({ ecosystem, authToken });
+ teamSlug,
+}: {
+ ecosystem: Ecosystem;
+ authToken: string;
+ teamSlug: string;
+}) {
+ const { partners, isPending } = usePartners({
+ ecosystem,
+ authToken,
+ });
if (isPending) {
return (
@@ -59,6 +67,7 @@ export function PartnersTable({
partner={partner}
ecosystem={ecosystem}
authToken={authToken}
+ teamSlug={teamSlug}
/>
))}
@@ -70,8 +79,10 @@ export function PartnersTable({
function PartnerRow(props: {
partner: Partner;
ecosystem: Ecosystem;
+ teamSlug: string;
authToken: string;
}) {
+ const router = useDashboardRouter();
const { mutateAsync: deletePartner, isPending: isDeleting } =
useDeletePartner(
{
@@ -117,22 +128,21 @@ function PartnerRow(props: {
-
{
+ router.push(
+ `/team/${props.teamSlug}/~/ecosystem/${props.ecosystem.slug}/configuration/partners/${props.partner.id}/edit`,
+ );
+ }}
>
-
-
- Edit
-
-
+
+
Edit
+
{
+ if (data !== undefined) {
+ return data > 0;
+ }
+ return true;
+ },
+ {
+ message: "Invalid chain ID",
+ },
+ ),
+ contractAddress: z
+ .string()
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data))
+ .refine(
+ (data) => {
+ if (data) {
+ return isAddress(data);
+ }
+ return true;
+ },
+ {
+ message: "Invalid contract address",
+ },
+ ),
+ selector: z
+ .string()
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data))
+ .refine(
+ (data) => {
+ if (data) {
+ return isHex(data) && data.length === 10; // 0x + 4 bytes for the selector (8 chars)
+ }
+ return true;
+ },
+ {
+ message: "Invalid function selector",
+ },
+ ),
+ maxValue: z
+ .string()
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data))
+ .refine(
+ (data) => {
+ if (data) {
+ return BigInt(data) >= 0;
+ }
+ return true;
+ },
+ {
+ message: "Invalid max value",
+ },
+ ),
+});
+
+const allowedTypedDataSchema = z
+ .object({
+ domain: z.string().refine((data) => data.length > 0, {
+ message: "Domain is required",
+ }),
+ verifyingContract: z
+ .string()
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data))
+ .refine(
+ (data) => {
+ if (data) {
+ return isAddress(data);
+ }
+ return true;
+ },
+ {
+ message: "Invalid verifying contract address",
+ },
+ ),
+ chainId: z
+ .number()
+ .optional()
+ .refine(
+ (data) => {
+ if (data !== undefined) {
+ return data > 0;
+ }
+ return true;
+ },
+ {
+ message: "Invalid chain ID",
+ },
+ ),
+ primaryType: z.string().optional(),
+ })
+ .transform((data) => {
+ return {
+ ...data,
+ verifyingContract:
+ data.verifyingContract && data.verifyingContract.length > 0
+ ? data.verifyingContract
+ : undefined,
+ primaryType:
+ data.primaryType && data.primaryType.length > 0
+ ? data.primaryType
+ : undefined,
+ };
+ });
+
+const personalSignRestrictionSchema = z.discriminatedUnion("messageType", [
+ z.object({
+ messageType: z.literal("userOp"),
+ allowedTransactions: z
+ .array(allowedTransactionSchema)
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data)),
+ }),
+ z.object({
+ messageType: z.literal("other"),
+ message: z.string().optional(),
+ }),
+]);
+
+const allowedOperationsSchema = z.discriminatedUnion("signMethod", [
+ z.object({
+ signMethod: z.literal("eth_signTransaction"),
+ allowedTransactions: z
+ .array(allowedTransactionSchema)
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data)),
+ }),
+ z.object({
+ signMethod: z.literal("eth_signTypedData_v4"),
+ allowedTypedData: z
+ .array(allowedTypedDataSchema)
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data)),
+ }),
+ z.object({
+ signMethod: z.literal("personal_sign"),
+ allowedPersonalSigns: z
+ .array(personalSignRestrictionSchema)
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data)),
+ }),
+]);
+
export const partnerFormSchema = z
.object({
name: z
@@ -33,6 +182,7 @@ export const partnerFormSchema = z
),
accessControlEnabled: z.boolean().default(false),
serverVerifierEnabled: z.boolean().default(false),
+ allowedOperationsEnabled: z.boolean().default(false),
accessControl: z
.object({
serverVerifier: z
@@ -48,6 +198,10 @@ export const partnerFormSchema = z
.optional(),
})
.optional(),
+ allowedOperations: z
+ .array(allowedOperationsSchema)
+ .optional()
+ .transform((data) => (data?.length === 0 ? undefined : data)),
})
.optional(),
})
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchEcosystem.ts b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchEcosystem.ts
new file mode 100644
index 00000000000..f51d70b2bf9
--- /dev/null
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchEcosystem.ts
@@ -0,0 +1,34 @@
+import { API_SERVER_URL } from "@/constants/env";
+import type { Ecosystem } from "../../../../types";
+
+/**
+ * Fetches ecosystem data from the server
+ */
+export async function fetchEcosystem(args: {
+ teamIdOrSlug: string;
+ slug: string;
+ authToken: string;
+}): Promise {
+ const { teamIdOrSlug, slug, authToken } = args;
+ const res = await fetch(
+ `${API_SERVER_URL}/v1/teams/${teamIdOrSlug}/ecosystem-wallet/${slug}`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ next: {
+ revalidate: 0,
+ },
+ },
+ );
+
+ if (!res.ok) {
+ const data = await res.json();
+ console.error(data);
+ throw new Error(
+ data?.message ?? data?.error?.message ?? "Failed to fetch ecosystem",
+ );
+ }
+
+ return (await res.json()).result as Ecosystem;
+}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchPartnerDetails.ts b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchPartnerDetails.ts
new file mode 100644
index 00000000000..395683a67ee
--- /dev/null
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchPartnerDetails.ts
@@ -0,0 +1,32 @@
+import type { Ecosystem, Partner } from "../../../../types";
+
+export async function fetchPartnerDetails(args: {
+ authToken: string;
+ ecosystem: Ecosystem;
+ partnerId: string;
+}): Promise {
+ const { authToken, ecosystem, partnerId } = args;
+
+ try {
+ const response = await fetch(
+ `${ecosystem.url}/${ecosystem.id}/partner/${partnerId}`,
+ {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch partner details: ${response.status} - ${response.statusText}`,
+ );
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error("Error fetching partner:", error);
+ throw error;
+ }
+}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchPartners.ts b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchPartners.ts
new file mode 100644
index 00000000000..32c6fa54b0c
--- /dev/null
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/fetchPartners.ts
@@ -0,0 +1,34 @@
+import type { Ecosystem, Partner } from "../../../../types";
+
+/**
+ * Fetches partners for an ecosystem
+ */
+export async function fetchPartners({
+ ecosystem,
+ authToken,
+}: {
+ ecosystem: Ecosystem;
+ authToken: string;
+}): Promise {
+ const res = await fetch(`${ecosystem.url}/${ecosystem.id}/partners`, {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ next: {
+ revalidate: 0,
+ },
+ });
+
+ if (!res.ok) {
+ const data = await res.json();
+ console.error(data);
+ throw new Error(
+ data?.message ?? data?.error?.message ?? "Failed to fetch partners",
+ );
+ }
+
+ const partners = (await res.json()) as Partner[];
+ return partners.sort(
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
+ );
+}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/partners/[partner_id]/edit/page.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/partners/[partner_id]/edit/page.tsx
new file mode 100644
index 00000000000..2905f97f030
--- /dev/null
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/partners/[partner_id]/edit/page.tsx
@@ -0,0 +1,96 @@
+import {} from "@/components/ui/breadcrumb";
+import { getAuthToken } from "../../../../../../../../../../../api/lib/getAuthToken";
+import { loginRedirect } from "../../../../../../../../../../../login/loginRedirect";
+import { UpdatePartnerForm } from "../../../components/client/update-partner-form.client";
+import { fetchEcosystem } from "../../../hooks/fetchEcosystem";
+import { fetchPartnerDetails } from "../../../hooks/fetchPartnerDetails";
+
+export default async function EditPartnerPage({
+ params,
+}: {
+ params: Promise<{ slug: string; team_slug: string; partner_id: string }>;
+}) {
+ const { slug, team_slug, partner_id } = await params;
+ const authToken = await getAuthToken();
+
+ if (!authToken) {
+ loginRedirect(`/team/${team_slug}/~/ecosystem/${slug}/configuration`);
+ }
+
+ const teamSlug = team_slug;
+ const ecosystemSlug = slug;
+ const partnerId = partner_id;
+
+ try {
+ const ecosystem = await fetchEcosystem({
+ teamIdOrSlug: teamSlug,
+ slug: ecosystemSlug,
+ authToken,
+ });
+
+ try {
+ // TODO re-enable this once IAW service is re deployed
+ const partner = await fetchPartnerDetails({
+ ecosystem,
+ partnerId,
+ authToken,
+ });
+ // const partners = await fetchPartners({
+ // ecosystem,
+ // authToken,
+ // });
+
+ // const partner = partners.find((p) => p.id === partnerId);
+
+ if (!partner) {
+ return (
+
+
+
+ Error
+
+
Could not load partner details. Please try again.
+
+
+ );
+ }
+
+ return (
+
+
+
+ Edit Partner: {partner.name}
+
+
+
+
+ );
+ } catch (partnerError) {
+ console.error("Error fetching partner:", partnerError);
+ return (
+
+
+
+ Error
+
+
Could not load partner details. Please try again.
+
+
+ );
+ }
+ } catch (ecosystemError) {
+ console.error("Error fetching ecosystem:", ecosystemError);
+ return (
+
+
+
Error
+
Could not load ecosystem. Please try again.
+
+
+ );
+ }
+}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/hooks/use-partners.ts b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/hooks/use-partners.ts
index 50f6372f045..6a947ac5fc8 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/hooks/use-partners.ts
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/hooks/use-partners.ts
@@ -1,5 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import type { Ecosystem, Partner } from "../../../types";
+import { fetchPartners } from "../configuration/hooks/fetchPartners";
export function usePartners({
ecosystem,
@@ -8,25 +9,7 @@ export function usePartners({
const partnersQuery = useQuery({
queryKey: ["ecosystem", ecosystem.id, "partners"],
queryFn: async () => {
- const res = await fetch(`${ecosystem.url}/${ecosystem.id}/partners`, {
- headers: {
- Authorization: `Bearer ${authToken}`,
- },
- });
-
- if (!res.ok) {
- const data = await res.json();
- console.error(data);
- throw new Error(
- data?.message ?? data?.error?.message ?? "Failed to fetch ecosystems",
- );
- }
-
- const partners = (await res.json()) as Partner[];
- return partners.sort(
- (a, b) =>
- new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
- );
+ return fetchPartners({ ecosystem, authToken });
},
retry: false,
});
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/types.ts b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/types.ts
index 610494417b6..f2d0f66d3be 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/types.ts
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/types.ts
@@ -60,5 +60,52 @@ export type Partner = {
url: string;
headers?: { key: string; value: string }[];
};
+ allowedOperations?: AllowedOperations[];
};
};
+
+type AllowedArgument = {
+ offset: number;
+ type: "address" | "uint256" | "bytes32" | "bool" | "string";
+ comparisonOperator: "eq" | "neq" | "gt" | "gte" | "lt" | "lte";
+ value: string;
+};
+
+type AllowedTransaction = {
+ chainId: number;
+ contractAddress?: string;
+ selector?: string;
+ arguments?: AllowedArgument[];
+ maxValue?: string;
+};
+
+type AllowedTypedData = {
+ domain: string;
+ verifyingContract?: string;
+ chainId?: number;
+ primaryType?: string;
+};
+
+type PersonalSignRestriction =
+ | {
+ messageType: "userOp";
+ allowedTransactions?: AllowedTransaction[];
+ }
+ | {
+ messageType: "other";
+ message?: string;
+ };
+
+type AllowedOperations =
+ | {
+ signMethod: "eth_signTransaction";
+ allowedTransactions?: AllowedTransaction[];
+ }
+ | {
+ signMethod: "eth_signTypedData_v4";
+ allowedTypedData?: AllowedTypedData[];
+ }
+ | {
+ signMethod: "personal_sign";
+ allowedPersonalSigns?: PersonalSignRestriction[];
+ };