Skip to content

ChainSafe/canton-snap

Repository files navigation

Canton Snap

MetaMask Snap for non-custodial Canton Network signing.

Derives secp256k1 keys from the user's MetaMask seed phrase and signs Canton transactions with SHA-256 + ASN.1 DER encoding — the signature format Canton's Interactive Submission API requires but MetaMask cannot produce natively.

Architecture

MetaMask (encrypted vault, holds seed)
    │
    └─ Canton Snap (sandboxed)
         ├─ Derives key at m/44'/60'/1'/0/0
         ├─ Signs with SHA-256 + ECDSA + DER
         ├─ Shows confirmation dialog
         └─ Returns signature to dApp

The Canton dApp (packages/dapp) is the browser frontend. It drives MetaMask + the snap for key operations, and talks to the Canton middleware REST API for registration and transaction flows.

Snap RPC Methods

Method Purpose Dialog
canton_getPublicKey Export compressed pubkey + SPKI DER + fingerprint Yes
canton_signTopology Sign topology hash during registration Yes
canton_signHash Sign a 32-byte hash, return DER signature Yes
canton_getFingerprint Quick fingerprint lookup No

Project Structure

canton-snap/
├── packages/
│   ├── snap/                       # MetaMask Snap — pure signing oracle
│   │   ├── src/
│   │   │   ├── index.ts            # onRpcRequest handler
│   │   │   ├── keyDerivation.ts    # BIP-44 key derivation from MetaMask seed
│   │   │   ├── dialogs.ts          # Confirmation dialog builders
│   │   │   ├── types.ts            # RPC param/response interfaces
│   │   │   ├── spki.ts             # Compressed pubkey → SPKI DER
│   │   │   ├── fingerprint.ts      # SPKI DER → Canton multihash fingerprint
│   │   │   └── sign.ts             # (privateKey, hash) → DER signature
│   │   ├── test/
│   │   │   ├── vectors.json        # Go-generated cross-validation vectors
│   │   │   ├── crypto.test.ts      # Crypto unit tests
│   │   │   ├── index.test.js       # Snap integration tests
│   │   │   └── setup.js
│   │   ├── snap.manifest.json
│   │   └── snap.config.ts
│   └── dapp/                       # Canton dApp — React 19 + Vite
│       ├── src/
│       │   ├── App.tsx             # State-based router
│       │   ├── components/         # TopBar, WalletMenu, NetworkSwitcher, …
│       │   ├── hooks/              # useMetaMask, useSnap, useRegistration
│       │   ├── lib/                # config, ethereum, middleware, cn
│       │   └── pages/              # LandingPage, RegistrationChoicePage, …
│       ├── index.html
│       └── vite.config.ts
├── docs/
│   └── testing-with-middleware.md  # Local dev setup and testing guide
├── designs/                        # UI design mockups (SVG)
├── eslint.config.js                # ESLint 9 flat config (all packages)
├── .env.example                    # Points to per-package .env.example files
└── package.json                    # Workspace root — scripts for all packages

Development

Requires MetaMask Flask — local snaps are rejected by the standard MetaMask extension. Flask is needed until the snap is published to npm. Run it in a dedicated browser profile where the standard MetaMask extension is not installed to avoid window.ethereum conflicts.

npm install

# Copy env templates for each package
cp packages/snap/.env.example packages/snap/.env
cp packages/dapp/.env.example packages/dapp/.env

# Build snap + dApp
npm run build

# Start both servers (snap on 4040, dApp on 3000)
npm run serve

# Or individually:
npm run serve:snap             # snap dev server
npm run dev:dapp               # dApp Vite dev server
npm run watch:snap             # snap with hot-reload

VITE_SNAP_PORT must match in both .env files (default: 4040).

See docs/testing-with-middleware.md for the full local setup guide including middleware integration.

Quality

npm run lint                   # ESLint across all packages
npm run lint:fix               # Auto-fix lint errors
npm run format                 # Prettier format
npm run format:check           # Check formatting without writing

Tests

npm test                       # Crypto cross-validation unit tests
npm run test:snap              # Full snap integration tests (jest + snaps-jest)

Snap Permissions

  • snap_getEntropy — derive snap-scoped Canton signing keys
  • snap_dialog — confirmation dialogs
  • snap_manageState — persist registered fingerprints
  • endowment:rpc (dapps: true) — accept RPC from dApps
  • No network access

Dependencies

Snap (packages/snap):

dApp (packages/dapp):

Release

Snap releases are driven by release-please and published to npm via Trusted Publishing — no NPM_TOKEN in the repo.

Scope: only packages/snap. Commits that don't touch packages/snap/ are ignored. The dApp will have its own pipeline later.

  1. Land conventional commits (feat:, fix:, feat!:) touching packages/snap/.
  2. Release Please opens a release PR (chore(main): release snap X.Y.Z).
  3. Merge it → tag snap-vX.Y.Z, GitHub release, npm publish with provenance.

Manual trigger: Actions → Release Please → Run workflow.

Installing the Published Snap

Snap ID: npm:@chainsafe/canton-snap. Works with standard MetaMask (no Flask).

const SNAP_ID = "npm:@chainsafe/canton-snap";

await window.ethereum.request({
  method: "wallet_requestSnaps",
  params: { [SNAP_ID]: { version: "^0.2.0" } },
});

await window.ethereum.request({
  method: "wallet_invokeSnap",
  params: { snapId: SNAP_ID, request: { method: "canton_getFingerprint" } },
});

Check install status with wallet_getSnaps.

About

MetaMask Snap for non-custodial Canton Network signing

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors