From 23c5a98b4ebaa797498d61d4a84655e2ec1557bd Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem <150973162+zeemscript@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:30:40 +0100 Subject: [PATCH 1/2] feat(sep24): validate interactive flow parameters --- src/routes/anchors.ts | 5 ++--- src/schemas/sep24.ts | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/schemas/sep24.ts diff --git a/src/routes/anchors.ts b/src/routes/anchors.ts index c153a07..cc58cf8 100644 --- a/src/routes/anchors.ts +++ b/src/routes/anchors.ts @@ -20,6 +20,7 @@ import { } from "../lib/pagination"; import { serializeAnchorSession } from "../serializers"; import { validateAsset } from "../services/assets"; +import { sep24InteractiveSchema } from "../schemas/sep24"; export default async function anchorRoutes(app: FastifyInstance) { // Every anchor route that reaches an anchor gets an explicit budget so a @@ -79,9 +80,7 @@ export default async function anchorRoutes(app: FastifyInstance) { // -- start deposit / withdraw ----------------------------------------------- async function start(kind: "deposit" | "withdrawal", req: any) { const auth = requireUser(req); - const body = z - .object({ assetCode: z.string().min(1), anchorName: z.string().optional() }) - .parse(req.body); + const body = sep24InteractiveSchema.parse(req.body); // Validate that the requested asset is supported. validateAsset(body.assetCode); diff --git a/src/schemas/sep24.ts b/src/schemas/sep24.ts new file mode 100644 index 0000000..9cc5360 --- /dev/null +++ b/src/schemas/sep24.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; + +const memoType = z.enum(["text", "id", "hash"]); + +/** Shared SEP-24 parameter contract for deposit and withdrawal starts. */ +export const sep24InteractiveSchema = z + .object({ + assetCode: z.string().trim().min(1).max(12), + anchorName: z.string().trim().min(1).max(120).optional(), + account: z.string().regex(/^G[A-Z2-7]{55}$/).optional(), + memo: z.string().trim().min(1).max(64).optional(), + memoType: memoType.optional(), + walletName: z.string().trim().min(1).max(120).optional(), + }) + .superRefine((value, ctx) => { + if (value.memo && !value.memoType) { + ctx.addIssue({ code: "custom", path: ["memoType"], message: "memoType is required when memo is supplied" }); + } + if (value.memoType && !value.memo) { + ctx.addIssue({ code: "custom", path: ["memo"], message: "memo is required when memoType is supplied" }); + } + }); From 311134a6020667c956fe9783d8ae083ca40c86a9 Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem <150973162+zeemscript@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:10:43 +0100 Subject: [PATCH 2/2] test(sep24): cover interactive parameter validation --- tests/routes/sep24.test.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/routes/sep24.test.ts diff --git a/tests/routes/sep24.test.ts b/tests/routes/sep24.test.ts new file mode 100644 index 0000000..8235d8d --- /dev/null +++ b/tests/routes/sep24.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { sep24InteractiveSchema } from "../../src/schemas/sep24"; + +describe("SEP-24 interactive parameters", () => { + it("accepts a valid asset and memo pair", () => { + const result = sep24InteractiveSchema.safeParse({ + assetCode: "USDC", + account: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + memo: "invoice-42", + memoType: "text", + }); + expect(result.success).toBe(true); + }); + + it("rejects a memo without a memo type", () => { + const result = sep24InteractiveSchema.safeParse({ assetCode: "XLM", memo: "42" }); + expect(result.success).toBe(false); + }); + + it("rejects an invalid account and unsupported memo type", () => { + const result = sep24InteractiveSchema.safeParse({ + assetCode: "XLM", + account: "not-a-stellar-account", + memo: "42", + memoType: "binary", + }); + expect(result.success).toBe(false); + }); +});