Home / MeshJS / multisig
May 03, 8-9 AM (0)
May 03, 9-10 AM (0)
May 03, 10-11 AM (0)
May 03, 11-12 PM (0)
May 03, 12-1 PM (0)
May 03, 1-2 PM (0)
May 03, 2-3 PM (0)
May 03, 3-4 PM (0)
May 03, 4-5 PM (0)
May 03, 5-6 PM (0)
May 03, 6-7 PM (0)
May 03, 7-8 PM (0)
May 03, 8-9 PM (0)
May 03, 9-10 PM (0)
May 03, 10-11 PM (0)
May 03, 11-12 AM (0)
May 04, 12-1 AM (0)
May 04, 1-2 AM (0)
May 04, 2-3 AM (0)
May 04, 3-4 AM (0)
May 04, 4-5 AM (0)
May 04, 5-6 AM (0)
May 04, 6-7 AM (0)
May 04, 7-8 AM (0)
May 04, 8-9 AM (0)
May 04, 9-10 AM (0)
May 04, 10-11 AM (0)
May 04, 11-12 PM (0)
May 04, 12-1 PM (0)
May 04, 1-2 PM (0)
May 04, 2-3 PM (0)
May 04, 3-4 PM (0)
May 04, 4-5 PM (0)
May 04, 5-6 PM (0)
May 04, 6-7 PM (0)
May 04, 7-8 PM (0)
May 04, 8-9 PM (0)
May 04, 9-10 PM (0)
May 04, 10-11 PM (0)
May 04, 11-12 AM (0)
May 05, 12-1 AM (0)
May 05, 1-2 AM (0)
May 05, 2-3 AM (0)
May 05, 3-4 AM (0)
May 05, 4-5 AM (0)
May 05, 5-6 AM (0)
May 05, 6-7 AM (0)
May 05, 7-8 AM (0)
May 05, 8-9 AM (0)
May 05, 9-10 AM (0)
May 05, 10-11 AM (0)
May 05, 11-12 PM (0)
May 05, 12-1 PM (0)
May 05, 1-2 PM (0)
May 05, 2-3 PM (0)
May 05, 3-4 PM (0)
May 05, 4-5 PM (0)
May 05, 5-6 PM (0)
May 05, 6-7 PM (0)
May 05, 7-8 PM (0)
May 05, 8-9 PM (0)
May 05, 9-10 PM (0)
May 05, 10-11 PM (0)
May 05, 11-12 AM (0)
May 06, 12-1 AM (0)
May 06, 1-2 AM (0)
May 06, 2-3 AM (0)
May 06, 3-4 AM (0)
May 06, 4-5 AM (0)
May 06, 5-6 AM (0)
May 06, 6-7 AM (0)
May 06, 7-8 AM (0)
May 06, 8-9 AM (0)
May 06, 9-10 AM (0)
May 06, 10-11 AM (0)
May 06, 11-12 PM (0)
May 06, 12-1 PM (0)
May 06, 1-2 PM (0)
May 06, 2-3 PM (0)
May 06, 3-4 PM (0)
May 06, 4-5 PM (0)
May 06, 5-6 PM (0)
May 06, 6-7 PM (0)
May 06, 7-8 PM (0)
May 06, 8-9 PM (0)
May 06, 9-10 PM (0)
May 06, 10-11 PM (0)
May 06, 11-12 AM (0)
May 07, 12-1 AM (0)
May 07, 1-2 AM (0)
May 07, 2-3 AM (0)
May 07, 3-4 AM (0)
May 07, 4-5 AM (0)
May 07, 5-6 AM (0)
May 07, 6-7 AM (0)
May 07, 7-8 AM (0)
May 07, 8-9 AM (0)
May 07, 9-10 AM (0)
May 07, 10-11 AM (0)
May 07, 11-12 PM (0)
May 07, 12-1 PM (0)
May 07, 1-2 PM (0)
May 07, 2-3 PM (0)
May 07, 3-4 PM (0)
May 07, 4-5 PM (0)
May 07, 5-6 PM (0)
May 07, 6-7 PM (0)
May 07, 7-8 PM (0)
May 07, 8-9 PM (0)
May 07, 9-10 PM (0)
May 07, 10-11 PM (0)
May 07, 11-12 AM (0)
May 08, 12-1 AM (0)
May 08, 1-2 AM (0)
May 08, 2-3 AM (0)
May 08, 3-4 AM (0)
May 08, 4-5 AM (0)
May 08, 5-6 AM (0)
May 08, 6-7 AM (0)
May 08, 7-8 AM (0)
May 08, 8-9 AM (0)
May 08, 9-10 AM (0)
May 08, 10-11 AM (0)
May 08, 11-12 PM (0)
May 08, 12-1 PM (0)
May 08, 1-2 PM (0)
May 08, 2-3 PM (0)
May 08, 3-4 PM (0)
May 08, 4-5 PM (0)
May 08, 5-6 PM (0)
May 08, 6-7 PM (0)
May 08, 7-8 PM (0)
May 08, 8-9 PM (0)
May 08, 9-10 PM (0)
May 08, 10-11 PM (0)
May 08, 11-12 AM (0)
May 09, 12-1 AM (0)
May 09, 1-2 AM (0)
May 09, 2-3 AM (0)
May 09, 3-4 AM (0)
May 09, 4-5 AM (0)
May 09, 5-6 AM (0)
May 09, 6-7 AM (0)
May 09, 7-8 AM (0)
May 09, 8-9 AM (0)
May 09, 9-10 AM (0)
May 09, 10-11 AM (0)
May 09, 11-12 PM (0)
May 09, 12-1 PM (0)
May 09, 1-2 PM (0)
May 09, 2-3 PM (0)
May 09, 3-4 PM (0)
May 09, 4-5 PM (0)
May 09, 5-6 PM (0)
May 09, 6-7 PM (0)
May 09, 7-8 PM (0)
May 09, 8-9 PM (0)
May 09, 9-10 PM (0)
May 09, 10-11 PM (0)
May 09, 11-12 AM (0)
May 10, 12-1 AM (0)
May 10, 1-2 AM (0)
May 10, 2-3 AM (0)
May 10, 3-4 AM (0)
May 10, 4-5 AM (0)
May 10, 5-6 AM (0)
May 10, 6-7 AM (0)
May 10, 7-8 AM (5)
May 10, 8-9 AM (0)
5 commits this week May 03, 2026 - May 10, 2026
chore(ui): a11y skip-link, useMemo transaction parse, cleanup deletions
Small UI/hook cleanup that fell out of the audit:

- overall-layout: add skip-link + main-content anchor; aria-label on the
  main form. Trims unused legacy nav code.
- transaction-card: useMemo the JSON.parse(transaction.txJson) so the
  parse only runs when txJson changes, not on every render. Removes a
  dead `import { get } from \"http\"` that was sneaking into the client
  bundle.
- signable-card: defensive parse for legacy payload shapes.
- card-show-signers, signing/index: small render fixes.
- ImgDragAndDrop, MeshProviderClient, BotManagementCard, background.tsx:
  drop dead state vars / unused imports surfaced by the audit.
- useAppWallet, useMultisigWallet: stable returns; missing-wallet path
  no longer spins indefinitely.
- Delete `src/components/multisig/proxy/ProxyControlExample.tsx`. It was
  example-only code, not exported from the proxy index, never rendered
  anywhere. The barrel import is updated.

Test plan
- 165/165 staged-suite tests pass on top of #237
- Typecheck clean

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
refactor(wallet-flow): stable signerIds for React keys; tripwire test
The new-wallet-flow and wallet-migration flow used the raw signer
address as a React key in ReviewSignersCard. Two issues:

1. The address can be empty/undefined while the user is editing, which
   makes the key non-unique → React reuses inputs across rows, swapping
   names and addresses while typing.
2. Two signers can momentarily share the same partial input, again
   producing duplicate keys.

Fix: every row gets a stable opaque `signerId` generated when the row
is created; the component uses `signerId` as the React key. Address
becomes regular state, free to be empty/duplicate transiently without
breaking React identity.

The same fix applied to both `useWalletFlowState` (new wallet) and
`useMigrationWalletFlowState` (migration). The shared `signerRows.ts`
module emits the id and keeps the parallel arrays in sync.

`reviewSignersCardKey.test.ts` is a tripwire suite that:
- greps the source to assert the raw address is never used as a key
- verifies both flow-state hooks expose signerIds parallel to
  signersAddresses

This catches future regressions structurally — the type system can't
enforce \"don't use address as key\", but a regex over source can.

Test plan
- 171/171 tests pass deterministically on top of #237
- Typecheck clean

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
feat(server): AuditLog table, DB indexes, security hardening, observability
Comprehensive server-side hardening pass: closes auth/security gaps,
adds an append-only AuditLog for security-relevant events, indexes
frequently queried columns, centralizes ctx typing, and lands shared
auth helpers.

## Database (already deployed to prod via prisma migrate deploy)

- New `AuditLog` table for append-only security audit trail
  (auth flows, wallet/transaction mutations, privilege grants, signer
  changes). Five indexes for the common access patterns.
- Btree indexes on Wallet/NewWallet `ownerAddress`, Signable+Transaction
  `walletId`/`state`/`(walletId, state)`, Proxy `walletId`/`userId`/
  `(walletId, isActive)`/`(userId, isActive)`, Ballot `walletId`,
  BalanceSnapshot `walletId`/`(walletId, snapshotDate)`.
- GIN indexes on Wallet/NewWallet `signersAddresses` (array_ops) — the
  signer-membership query was a full table scan.
- Restored `Crowdfund` model declaration (production drift: table exists
  in prod but was never declared in main's schema; see PR description
  for full archaeology). Marked as retained-but-unused.
- WalletBotAccess: `@@unique` -> `@@id` to match prod (drift from
  PR #207 / commit 1facdc3 where schema and migration disagreed at
  landing).
- Ballot.updatedAt: restored `@default(now()) @updatedAt` to match
  prod's column default (drift accumulated across multiple commits).
- Ballot.anchorUrls / anchorHashes: added `DEFAULT ARRAY[]::TEXT[]` to
  match the schema's `@default([])` annotation.

## Observability primitives

- `src/lib/observability/audit.ts` — `audit(db, event)` emitter; never
  throws (audit miss must not break user flow); redacts secrets in
  metadata before write.
- `src/lib/observability/logger.ts` — structured logger; JSON in prod,
  human-readable in dev; never logs raw tokens/signatures/cookies.

## Security fixes (Wave 1-3)

- Closed `ownerAddress === "all"` bypass in `assertWalletAccess`. The
  string "all" was being treated as a wildcard owner — any session
  could claim ownership of any wallet whose `ownerAddress` happened to
  contain that literal.
- `lookupMultisigWallet`: validate stake-credential-hash format before
  query (prevents prefix-match abuse and full-table scans on malformed
  input).
- Centralized rate-limit and request-guard surface (`src/lib/security/
  rateLimit.ts`, `requestGuards`). Bot routes now use bot-scoped
  rate limit; user routes use IP-scoped.
- `verifyJwt`: stricter token-type narrowing; explicit `isBotJwt`
  predicate.
- `walletSession`: tighter expiry handling, no implicit refresh.

## Auth helpers (Wave 8)

- New `src/server/api/auth.ts` consolidates `requireSessionAddress`,
  `getSessionAddresses`, and wallet-access checks that were duplicated
  in nearly every router. One source of truth, one place to extend.
- All routers and v1 API handlers migrated.

## ctx typing (Wave 2)

- New `AuthCtx` and `TRPCContext` exported from `src/server/api/trpc.ts`.
- All router helpers use `AuthCtx` instead of `any`.
- `protectedProcedure` middleware: type-narrows `sessionWallets`,
  `primaryWallet`, `sessionAddress` correctly.

## Audit emitters (Wave 5)

Wired into:
- auth flow (login success/failure, JWT mint, bot auth)
- wallet mutations (create, update, archive, transfer, signer changes)
- signable + transaction mutations (sign, reject, broadcast)
- bot privilege grants

All emitters fire after the underlying action and never block it.

## SSRF defense for `/api/v1/og`

The OG metadata endpoint now:
- requires https, denies non-allowlisted hosts
- DNS-resolves and rejects private/loopback/link-local addresses
- denies upstream redirects (no auto-follow)

`OG_ALLOWED_HOSTS` env var configures the allow list; "*" allows any
public host (still SSRF-guarded).

## Test infrastructure

- jest.config.mjs — moduleNameMapper for CSS, transformIgnorePatterns
  for ESM-only deps (superjson, @trpc, @meshsdk, jose, etc.)
- setupEnv.cjs — pre-test env bootstrap (SKIP_ENV_VALIDATION=1, dummy
  DB/JWT/Blockfrost values) so `src/env.js` doesn't throw on import.
- Frozen wall clock (`Date.now`/`new Date`) for byte-identical test
  runs; real timer APIs preserved.
- `__mocks__/styleMock.cjs` — CSS imports mock for jest.

## Tests

- New: `og.test.ts` (SSRF tripwire suite — 9 cases for the og handler).
- New: `signing.test.ts` (source tripwires preventing the
  `return true ? signature : undefined` regression and similar).
- Updated existing tests to match Jest 30 strict mock typing
  (jest.fn<...>() generics) and new ctx fields.

## Verification

- Typecheck clean
- All 165 staged-suite tests pass deterministically across two runs
- Migration `20260510160404_audit_log_and_indexes` already applied to
  the multisig Supabase production DB — `prisma migrate deploy` on
  this branch is a no-op (idempotent).

Depends on: #236 (build fix; without it `next build --webpack` will
crash on `/wallets/[wallet]/transactions/new`).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
fix(build): lazy-init mainnet provider; remove global sideEffects:false
Two interacting issues caused the production build to crash on routes
that imported resolve-adahandle (notably /wallets/[wallet]/transactions/new):

1. resolve-adahandle.tsx called getProvider(1) at module top level. Under
   `next build` page-data collection, every server-rendered page imports
   that module, which constructs BlockfrostProvider(undefined) when
   NEXT_PUBLIC_BLOCKFROST_API_KEY_MAINNET isn't readable at build time
   (e.g. SKIP_ENV_VALIDATION=true). The constructor throws and the
   webpack runtime reports it as a generic "factory error".

2. next.config.js had `optimization.sideEffects: false` set globally,
   which tells webpack that *every* file is side-effect-free. That
   silently strips global CSS imports and any module-level initialization,
   masking issues like (1) until you hit a route that exercises them.

Fix:
- Lazy-init the mainnet provider with a cached singleton, so import is
  free and instantiation only happens when a caller actually resolves a
  handle (always client-side).
- Remove the global sideEffects:false override. Per-package sideEffects
  declarations in package.json are the correct mechanism; the global
  override was masking real bugs.
- Move swagger-ui-react CSS import from api-docs.tsx into _app.tsx so
  Next.js Pages Router's "global CSS only from _app" rule is satisfied.

Verified locally: `next build --webpack` completes; both
/wallets/[wallet]/transactions/new and /api-docs render.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
chore(ci): gate typecheck/test/build; add dependabot
PR checks were silently passing because typecheck, test, and build steps
were marked continue-on-error, so the gates only reported status, never
blocked merges. Drop continue-on-error from those three; keep it on lint
until the rule set is cleaned up.

Add dependabot config for npm + github-actions, with grouping for
@meshsdk/*, next/@next/*, prisma/@prisma/*, @trpc/*, and @types/*.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>