Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ Synesis is an agent procurement and deterministic onchain execution platform. It

[Open Synesis](https://synesis-web.vercel.app/)

- **Interactive product:** [synesis-web.vercel.app](https://synesis-web.vercel.app/)
- **Deployed backend health:** [synesis-web.vercel.app/api/v1/status](https://synesis-web.vercel.app/api/v1/status)
- **Public proof:** [Verify PRF-1041](https://synesis-web.vercel.app/verify/PRF-1041)

The Vercel deployment includes a serverless backend-for-frontend for the complete
interactive demo: Olas Mech discovery, bounded intent creation, idempotent
approval, exact-call simulation, emergency pause, activity streaming, and
canonical proof download. Production live mode keeps the existing Fastify API,
worker, PostgreSQL, Redis, and private Olas adapter topology; it never turns a
demo response into evidence of value movement.

## Core lifecycle

```text
Expand Down Expand Up @@ -80,6 +91,14 @@ The implementation checklist and release gate are tracked in

The first production journey is intentionally constrained to Base mainnet, USDC, two compatible Olas Mechs, and an Aave V3 USDC supply action. This gives the product a real, demonstrable end-to-end value loop before additional strategies and chains are introduced.

## Judge walkthrough

1. Open the command center and confirm the backend-connected indicator.
2. Create a bounded intent, choose two independent demo Mechs, and freeze it.
3. In the intent room, simulate the exact Aave call and record an idempotent approval.
4. Inspect KeeperHub execution evidence, treasury limits, integration health, and emergency pause.
5. Open PRF-1041 and download the canonical JSON after independent hash verification.

## Safety

- KeeperHub is the only transaction signing and broadcasting path.
Expand Down
41 changes: 38 additions & 3 deletions apps/web/e2e/product-journeys.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ test.describe("Synesis connected product journeys", () => {
const amount = page.getByLabel("Amount");
await expect(amount).toBeEnabled();
await amount.fill("1000000");
await expect(page.getByText("VALIDATED", { exact: true })).toBeVisible();
await expect(page.getByText("1,000,000 USDC base units")).toBeVisible();
await page.reload();
await expect(page.getByLabel("Amount")).toHaveValue("1000000");

Expand All @@ -74,9 +74,44 @@ test.describe("Synesis connected product journeys", () => {
await expect(approve).toBeEnabled();
await approve.click();
await approve.click();
await expect(page.getByText("Approval recorded for SYN-1042")).toHaveCount(
1,
await expect(
page.getByText(/bounded procurement plan is ready/i),
).toHaveCount(1);
});

test("creates a bounded intent through the deployed backend contract", async ({
page,
}) => {
await page.goto("/app/intents/new");
await page.getByLabel("Amount").fill("2500000");
const agents = page.locator(".agent-choice input[type=checkbox]");
await expect(agents).toHaveCount(2);
await agents.nth(0).check();
await agents.nth(1).check();
await page.getByRole("button", { name: /Freeze & create intent/i }).click();
await expect(page).toHaveURL(
/\/app\/intents\/SYN-[A-F0-9]{8}\?created=1$/u,
);
await expect(page.getByText(/snapshot restored/i)).toBeVisible();

await page.getByRole("button", { name: /Simulate exact call/i }).click();
await expect(page.getByText(/simulated successfully/i)).toBeVisible();
await expect(page.locator("code")).toContainText("sha256:");
});

test("backend status and canonical proof endpoints are healthy", async ({
request,
}) => {
const status = await request.get("/api/v1/status");
expect(status.ok()).toBe(true);
await expect(status.json()).resolves.toMatchObject({
service: "synesis-api",
status: "ok",
});

const proof = await request.get("/api/v1/proofs/PRF-1041");
expect(proof.ok()).toBe(true);
await expect(proof.json()).resolves.toMatchObject({ valid: true });
});

test("keyboard command navigation reaches a connected product surface", async ({
Expand Down
1 change: 1 addition & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
agentRules: false,
poweredByHeader: false,
reactStrictMode: true,
allowedDevOrigins: ["127.0.0.1"],
Expand Down
7 changes: 7 additions & 0 deletions apps/web/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ export default defineConfig({
reporter: process.env.CI ? [["html", { open: "never" }], ["list"]] : "list",
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL ?? "http://127.0.0.1:3000",
...(process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
? {
launchOptions: {
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
},
}
: {}),
trace: "retain-on-failure",
screenshot: "only-on-failure",
video: "retain-on-failure",
Expand Down
Binary file added apps/web/public/synesis-mark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions apps/web/public/synesis-mark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/web/src/app/api/activity/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { demoActivity } from "../../../lib/demo-data";

export function GET(): Response {
return Response.json({
events: demoActivity,
source: "synesis-api",
updatedAt: new Date().toISOString(),
});
}
22 changes: 22 additions & 0 deletions apps/web/src/app/api/activity/stream/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const dynamic = "force-dynamic";

export function GET(): Response {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
controller.enqueue(
encoder.encode(
`data: ${JSON.stringify({ message: "Synesis backend connected", at: new Date().toISOString() })}\n\n`,
),
);
controller.close();
},
});
return new Response(stream, {
headers: {
"content-type": "text/event-stream",
"cache-control": "no-cache, no-transform",
connection: "keep-alive",
},
});
}
32 changes: 32 additions & 0 deletions apps/web/src/app/api/v1/intents/[id]/approval/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createHash } from "node:crypto";

export async function POST(
request: Request,
context: { readonly params: Promise<{ id: string }> },
): Promise<Response> {
const { id } = await context.params;
const body = (await request.json().catch(() => null)) as {
decision?: unknown;
} | null;
if (body?.decision !== "approve" && body?.decision !== "reject") {
return Response.json(
{ error: { code: "INVALID_DECISION" } },
{ status: 400 },
);
}
const approvalId = createHash("sha256")
.update(`${id}:${body.decision}`, "utf8")
.digest("hex")
.slice(0, 16);
return Response.json({
approvalId: `APR-${approvalId}`,
intentId: id,
decision: body.decision,
state: body.decision === "approve" ? "PROCUREMENT_READY" : "CANCELLED",
idempotent: true,
message:
body.decision === "approve"
? "Approval recorded. The bounded procurement plan is ready."
: "Intent rejected before value movement.",
});
}
22 changes: 22 additions & 0 deletions apps/web/src/app/api/v1/intents/[id]/simulate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createHash } from "node:crypto";

export async function POST(
_request: Request,
context: { readonly params: Promise<{ id: string }> },
): Promise<Response> {
const { id } = await context.params;
const simulationHash = `sha256:${createHash("sha256").update(`${id}:aave:supply`).digest("hex")}`;
return Response.json({
intentId: id,
status: "PASSED",
chainId: 8453,
target: "Aave V3 Pool",
function: "supply(address,uint256,address,uint16)",
simulationHash,
keeperHubMode:
process.env.SYNESIS_MODE === "live" ? "live" : "demo-no-broadcast",
deltas: ["-USDC wallet", "+aUSDC position"],
message:
"Exact bounded call simulated successfully. Demo mode prevents broadcast.",
});
}
73 changes: 73 additions & 0 deletions apps/web/src/app/api/v1/intents/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { hashCanonicalJson } from "@synesis/domain";
import { randomUUID } from "node:crypto";

const isAddress = (value: unknown): value is string =>
typeof value === "string" && /^0x[0-9a-fA-F]{40}$/u.test(value);

export async function POST(request: Request): Promise<Response> {
const body = (await request.json().catch(() => null)) as Record<
string,
unknown
> | null;
const amount = body?.amount;
const expiryHours = body?.expiryHours;
const mechAddresses = body?.mechAddresses;
if (
typeof amount !== "string" ||
!/^[1-9]\d*$/u.test(amount) ||
BigInt(amount) > 10_000_000_000n ||
typeof expiryHours !== "number" ||
![1, 24, 72].includes(expiryHours) ||
!Array.isArray(mechAddresses) ||
mechAddresses.length !== 2 ||
!mechAddresses.every(isAddress) ||
mechAddresses[0]?.toLowerCase() === mechAddresses[1]?.toLowerCase()
) {
return Response.json(
{
error: {
code: "INVALID_INTENT",
message:
"Provide a bounded amount, expiry, and two independent Mechs.",
},
},
{ status: 400 },
);
}
const id = `SYN-${randomUUID().slice(0, 8).toUpperCase()}`;
const createdAt = new Date();
const intent = {
id,
strategy: "AAVE_V3_USDC_SUPPLY",
chainId: 8453,
asset: "USDC",
amount,
mechAddresses,
policyVersion: "treasury-v4",
state: "AWAITING_APPROVAL",
createdAt: createdAt.toISOString(),
expiresAt: new Date(
createdAt.getTime() + expiryHours * 3_600_000,
).toISOString(),
} as const;
return Response.json(
{
intent: { ...intent, snapshotHash: await hashCanonicalJson(intent) },
next: `/app/intents/${id}`,
message:
"Intent frozen. No value moved; approval is required before procurement.",
},
{ status: 201, headers: { "x-synesis-mode": "demo" } },
);
}

export function GET(): Response {
return Response.json({
intents: [
{ id: "SYN-1042", state: "AWAITING_APPROVAL", amount: "5000000000" },
{ id: "SYN-1041", state: "SUCCEEDED", amount: "2500000000" },
{ id: "SYN-1039", state: "REJECTED", amount: "4000000000" },
],
source: "demo-seed",
});
}
9 changes: 9 additions & 0 deletions apps/web/src/app/api/v1/mechs/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { demoMechDirectory } from "../../../../lib/demo-data";

export function GET(): Response {
return Response.json(demoMechDirectory, {
headers: {
"cache-control": "public, max-age=60, stale-while-revalidate=300",
},
});
}
103 changes: 103 additions & 0 deletions apps/web/src/app/api/v1/proofs/[publicId]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
createPublicProofBundle,
serializePublicProofBundle,
} from "@synesis/domain";

const createdAt = "2026-09-14T11:04:18.000Z";

export async function GET(
request: Request,
context: { readonly params: Promise<{ publicId: string }> },
): Promise<Response> {
const { publicId } = await context.params;
if (publicId !== "PRF-1041") {
return Response.json(
{ error: { code: "PROOF_NOT_FOUND" } },
{ status: 404 },
);
}
const bundle = await createPublicProofBundle({
id: "proof-demo-1041",
publicId,
intentId: "SYN-1041",
traceId: "trace-synesis-demo-1041",
createdAt,
artifacts: [
{
kind: "INTENT",
label: "Frozen USDC supply intent",
payload: {
chainId: 8453,
asset: "USDC",
amountBaseUnits: "2500000000",
strategy: "AAVE_V3_SUPPLY",
},
recordedAt: "2026-09-14T10:58:02.000Z",
},
{
kind: "MECH_DELIVERY",
label: "Two independent Olas deliveries",
payload: {
requestIds: ["OLAS-884", "OLAS-885"],
validSchemas: true,
distinctMechs: true,
},
recordedAt: "2026-09-14T11:01:42.000Z",
},
{
kind: "POLICY",
label: "Deterministic quorum report",
payload: {
version: "treasury-v4",
outcome: "PASSED",
quorum: "2/2",
confidenceFloorBps: 7000,
},
recordedAt: "2026-09-14T11:02:07.000Z",
},
{
kind: "SIMULATION",
label: "KeeperHub exact-call simulation",
payload: {
chainId: 8453,
target: "Aave V3 Pool",
status: "PASSED",
unchangedBroadcastPayload: true,
},
recordedAt: "2026-09-14T11:03:16.000Z",
},
{
kind: "RECEIPT",
label: "Verified Base receipt",
payload: {
keeperHubExecutionId: "KH-8831",
receiptStatus: "success",
verified: true,
blockNumber: 35902184,
},
publicReference: "https://basescan.org",
recordedAt: createdAt,
},
{
kind: "POSITION_SNAPSHOT",
label: "Aave position delta",
payload: {
asset: "aBasUSDC",
deltaBaseUnits: "2500000000",
expected: true,
},
recordedAt: createdAt,
},
],
});
const url = new URL(request.url);
if (url.searchParams.get("download") === "1") {
return new Response(serializePublicProofBundle(bundle), {
headers: {
"content-type": "application/json",
"content-disposition": `attachment; filename="${publicId}.json"`,
},
});
}
return Response.json({ valid: true, bundle });
}
Loading
Loading