Skip to content
Open
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
35 changes: 35 additions & 0 deletions .agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Agent instructions (Mesh Multisig)

Project-specific context for AI coding agents. See also [.cursor/skills/multisig/SKILL.md](../.cursor/skills/multisig/SKILL.md) for the multisig Cursor skill.

## Stack and layout

- **Stack**: Next.js (Pages Router), TypeScript, tRPC, Prisma, PostgreSQL, Cardano (Mesh SDK). Auth: NextAuth (user) + JWT (API: wallet sign-in or bot keys).
- **API**: REST v1 under `/api/v1/*`. OpenAPI: `GET /api/swagger`. Interactive docs: `/api-docs`.
- **Key paths**: Pages in `src/pages/`, UI in `src/components/`, tRPC in `src/server/api/routers/`, REST handlers in `src/pages/api/v1/*.ts`, DB schema in `prisma/schema.prisma`.

## Build and test

- **Install**: `npm install`
- **Env**: Copy `.env.example` to `.env`; set `DATABASE_URL`, `JWT_SECRET`, Blockfrost keys, etc. For local DB: `docker compose -f docker-compose.dev.yml up -d postgres`
- **DB**: `npm run db:update` (format + push schema + generate client). Prisma Studio: `npm run db:studio`
- **Dev**: `npm run dev` → http://localhost:3000
- **Lint**: `npm run lint`
- **Tests**: `npm test` or `npm run test:ci` for CI

## Conventions

- **Wallet ID**: UUID from DB. **Address**: Cardano payment (or stake) address. Don’t confuse them.
- **Scripts**: Use `scripts/` (e.g. `scripts/bot-ref/`). Run TS with `npx tsx`.
- **New v1 endpoint**: Add handler in `src/pages/api/v1/<name>.ts`, apply CORS and rate limits, then add path and docs in `src/utils/swagger.ts`. If bots can call it, update the landing “Developers & Bots” section and `scripts/bot-ref/README.md` as needed.
- **Bot auth**: Implemented in `src/pages/api/v1/botAuth.ts` and `src/lib/auth/botKey.ts`, `botAccess.ts`. Bot keys created in-app (User → Create bot). One key → one `paymentAddress`; use `Authorization: Bearer <token>` for v1 after `POST /api/v1/botAuth`.

## Bot integration (machine-friendly)

- **OpenAPI**: `GET /api/swagger` (JSON).
- **Bot auth**: `POST /api/v1/botAuth` with body `{ "botKeyId", "secret", "paymentAddress" }` → `{ "token", "botId" }`. Use token as Bearer for `walletIds`, `pendingTransactions`, `freeUtxos`, `addTransaction`, `signTransaction`, etc. Reference client: `scripts/bot-ref/` (see README there).

## Docs to keep in sync

- Landing “Developers & Bots” section: `src/components/pages/homepage/index.tsx` (id `#developers-and-bots`).
- API/bot docs: `src/utils/swagger.ts`, `scripts/bot-ref/README.md`.
46 changes: 46 additions & 0 deletions .cursor/skills/multisig/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: multisig
description: Build and integrate with the Mesh Multisig (Cardano multisig wallet) codebase. Use when working on multisig wallets, bot API, v1 REST endpoints, wallet flows, governance, or Cardano treasury tooling.
---

# Multisig (Mesh)

## Project overview

- **Stack**: Next.js (Pages Router), tRPC, Prisma, Cardano (Mesh SDK).
- **Auth**: NextAuth (user) + JWT for API (wallet sign-in or bot keys).
- **API**: REST v1 under `/api/v1/*` (Swagger at `/api-docs`, spec at `/api/swagger`).

## Key areas

| Area | Location | Notes |
|------|----------|--------|
| Landing page | `src/components/pages/homepage/index.tsx` | Hero, features, DApps, Developers & Bots section |
| API docs (Swagger) | `src/pages/api-docs.tsx`, `src/utils/swagger.ts` | OpenAPI 3.0; add new paths in `swagger.ts` |
| Bot API | `src/pages/api/v1/botAuth.ts`, `src/lib/auth/botKey.ts`, `src/lib/auth/botAccess.ts` | Bot auth: POST `/api/v1/botAuth` with `botKeyId`, `secret`, `paymentAddress` |
| Reference bot client | `scripts/bot-ref/` | `bot-client.ts`; auth → walletIds, pendingTransactions, freeUtxos |
| Wallet flows | `src/components/pages/homepage/wallets/new-wallet-flow/`, `useWalletFlowState.tsx` | New wallet creation and invite flow |
| tRPC | `src/server/api/routers/`, `src/server/api/root.ts` | Wallets, bot routers |
| DB | `prisma/schema.prisma` | Wallet, BotKey, BotUser, etc. |

## Bot integration (machine-friendly)

- **OpenAPI spec (JSON)**: `GET /api/swagger` — use for codegen or automation.
- **Auth (bots)**: `POST /api/v1/botAuth`
Body: `{ "botKeyId": string, "secret": string, "paymentAddress": string, "stakeAddress"?: string }`
Response: `{ "token": string, "botId": string }`. Use `Authorization: Bearer <token>` for v1 endpoints.
- **Bot keys**: Created in-app (User → Create bot). One bot key can have one `paymentAddress`; same address cannot be used by another bot.
- **Scopes**: Bot keys have scope (e.g. `multisig:read`); `botAccess.ts` enforces wallet access for bots.
- **V1 endpoints used by bots**: `walletIds` (query `address` = bot’s `paymentAddress`), `pendingTransactions`, `freeUtxos`, `addTransaction`, `signTransaction`, etc. Same as wallet-authenticated calls but identity is the bot’s registered address.

## Conventions

- **Wallet ID**: UUID from DB; **address**: Cardano payment (or stake) address.
- **Scripts**: Reference scripts in `scripts/` (e.g. `scripts/bot-ref/`). Use `npx tsx` for TS scripts.
- **Env**: `JWT_SECRET` required for API tokens; bot keys stored hashed in DB.

## When editing

- Adding a new v1 endpoint: implement in `src/pages/api/v1/<name>.ts`, add path and CORS/rate limits, then add to `src/utils/swagger.ts` and document bot usage if applicable.
- Changing bot auth or scopes: update `botAuth.ts`, `botAccess.ts`, and landing “Developers & Bots” section plus `scripts/bot-ref/README.md` if needed.
- Landing page: human and bot-friendly docs live in the “Developers & Bots” section; keep OpenAPI URL and bot auth summary accurate.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ yarn-error.log*
.env
.env*.local

# bot ref client config (contains secret)
scripts/bot-ref/bot-config.json
# generated bot wallet for testing (mnemonic + address)
scripts/bot-ref/bot-wallet.json

# vercel
.vercel

Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Agent instructions

Project instructions for coding agents are in **[.agents/README.md](.agents/README.md)**.
2 changes: 1 addition & 1 deletion docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ services:
image: postgres:14-alpine
container_name: multisig-postgres-dev
ports:
- "5433:5432"
- "5434:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
Expand Down
14 changes: 12 additions & 2 deletions docker/init-db.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ set -e

echo "Initializing database..."

# This script runs automatically when PostgreSQL container starts for the first time
# Add any custom initialization SQL here if needed
# Roles required by migration 20251215090000_enable_rls_disable_postgrest (Supabase-style RLS)
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'anon') THEN
CREATE ROLE anon NOLOGIN;
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated') THEN
CREATE ROLE authenticated NOLOGIN;
END IF;
END \$\$;
EOSQL

echo "Database initialization complete."
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --watchAll=false",
"analyze": "ANALYZE=true npm run build"
"analyze": "ANALYZE=true npm run build",
"apply-project": "node scripts/apply-project-to-github.mjs"
},
"dependencies": {
"@auth/prisma-adapter": "^2.11.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
-- AlterTable
ALTER TABLE "NewWallet" ADD COLUMN "stakeCredentialHash" TEXT;
ALTER TABLE "NewWallet" ADD COLUMN "scriptType" TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
-- AlterTable
ALTER TABLE "Wallet" ADD COLUMN IF NOT EXISTS "ownerAddress" TEXT;

-- CreateTable
CREATE TABLE "BotKey" (
"id" TEXT NOT NULL,
"ownerAddress" TEXT NOT NULL,
"name" TEXT NOT NULL,
"keyHash" TEXT NOT NULL,
"scope" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "BotKey_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "BotUser" (
"id" TEXT NOT NULL,
"botKeyId" TEXT NOT NULL,
"paymentAddress" TEXT NOT NULL,
"stakeAddress" TEXT,
"displayName" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "BotUser_pkey" PRIMARY KEY ("id")
);

-- CreateEnum
CREATE TYPE "BotWalletRole" AS ENUM ('cosigner', 'observer');

-- CreateTable
CREATE TABLE "WalletBotAccess" (
"walletId" TEXT NOT NULL,
"botId" TEXT NOT NULL,
"role" "BotWalletRole" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "WalletBotAccess_pkey" PRIMARY KEY ("walletId","botId")
);

-- CreateIndex
CREATE INDEX "BotKey_ownerAddress_idx" ON "BotKey"("ownerAddress");

-- CreateIndex
CREATE UNIQUE INDEX "BotUser_botKeyId_key" ON "BotUser"("botKeyId");

-- CreateIndex
CREATE UNIQUE INDEX "BotUser_paymentAddress_key" ON "BotUser"("paymentAddress");

-- CreateIndex
CREATE INDEX "BotUser_paymentAddress_idx" ON "BotUser"("paymentAddress");

-- CreateIndex
CREATE INDEX "WalletBotAccess_walletId_idx" ON "WalletBotAccess"("walletId");

-- CreateIndex
CREATE INDEX "WalletBotAccess_botId_idx" ON "WalletBotAccess"("botId");

-- AddForeignKey
ALTER TABLE "BotUser" ADD CONSTRAINT "BotUser_botKeyId_fkey" FOREIGN KEY ("botKeyId") REFERENCES "BotKey"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE "Ballot"
ADD COLUMN "rationaleComments" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
43 changes: 43 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ model Wallet {
rawImportBodies Json?
migrationTargetWalletId String?
profileImageIpfsUrl String?
ownerAddress String?
}

model Transaction {
Expand Down Expand Up @@ -112,8 +113,10 @@ model Ballot {
choices String[]
anchorUrls String[] @default([])
anchorHashes String[] @default([])
rationaleComments String[] @default([])
type Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model Proxy {
Expand Down Expand Up @@ -178,3 +181,43 @@ model Contact {
@@index([walletId])
@@index([address])
}

model BotKey {
id String @id @default(cuid())
ownerAddress String // Human creator
name String
keyHash String
scope String // JSON array e.g. ["multisig:create","multisig:read","multisig:sign"]
createdAt DateTime @default(now())
botUser BotUser?

@@index([ownerAddress])
}

model BotUser {
id String @id @default(cuid())
botKeyId String @unique
paymentAddress String @unique // One bot, one address
stakeAddress String?
displayName String?
createdAt DateTime @default(now())
botKey BotKey @relation(fields: [botKeyId], references: [id], onDelete: Cascade)

@@index([paymentAddress])
}

enum BotWalletRole {
cosigner
observer
}

model WalletBotAccess {
walletId String
botId String
role BotWalletRole
createdAt DateTime @default(now())

@@unique([walletId, botId])
@@index([walletId])
@@index([botId])
}
Loading
Loading