Skip to content

Non-custodial signing: Prepare/Execute API MetaMask Snap #111

@salindne

Description

@salindne

Context

Builds on #109 (External Party Migration + Interactive Submission). Once external parties and InteractiveSubmission infrastructure are in place, this issue adds client-side signing so users can control their own keys rather than trusting the server.

Currently the server generates, stores, and signs with user keys (custodial model). This issue enables MetaMask Snap users to sign Canton transactions directly via a two-step prepare/execute API.

Architecture Decision: Dual-Mode

Two transfer paths coexist in the middleware:

  • Custodial (/eth): Standard MetaMask users. Server holds Canton key, signs on their behalf. Unchanged from today.
  • Non-custodial (/api/v2/transfer/*): Future MetaMask Snap users. Server prepares the Canton transaction, Snap signs locally, server executes. Server never sees private keys.

Registration determines key_mode. Both custodial and non-custodial MetaMask users coexist in the same system.

flowchart LR
    subgraph middleware [Middleware - ERC-20 Compatibility Layer]
        MM[MetaMask] -->|eth_sendRawTransaction| ETH["/eth endpoint"]
        ETH -->|server signs with custodial key| Canton1[Canton]
        Snap[MetaMask + Snap] -->|prepare / execute| API["/api/v2/transfer/*"]
        API -->|submit client signature| Canton1
    end
    subgraph direct [Direct Canton Access]
        Loop[Loop Wallet] -->|Canton Ledger API gRPC| Canton2[Canton]
    end
Loading

Note on Loop Wallet

Loop Wallet is a Canton-native wallet that talks directly to Canton via the Ledger API (gRPC). It does NOT route through this middleware. Loop Wallet compatibility is handled by #110 (Splice Token Standard contract rewrite). The middleware is exclusively for Ethereum/MetaMask users.

Sub-issues

Implementation is broken into 5 sub-issues with clear dependencies:

Sub-issue Title Depends on
#131 (A) Database -- Add key_mode column and public key fingerprint None
#132 (B) Canton SDK -- Expose PrepareTransfer and ExecuteTransfer separately None
#133 (C) HTTP API -- Prepare/Execute transfer endpoints A, B
#134 (D) Registration updates for non-custodial users A
#135 (E) Integration testing for prepare/execute flow C, D
graph TD
    A["#131 A: DB key_mode column"] --> C["#133 C: HTTP prepare/execute endpoints"]
    A --> D["#134 D: Registration updates"]
    B["#132 B: SDK prepare/execute split"] --> C
    C --> E["#135 E: Integration testing"]
    D --> E
Loading

A and B can be done in parallel. C depends on both. D depends on A. E depends on C and D.

Current Scope (this round)

  • Backend API endpoints (prepare/execute)
  • Database schema changes (key_mode column)
  • Canton SDK refactor (split prepare and execute)
  • Registration updates for non-custodial users
  • Integration testing with a Go test signer (simulates what a Snap would do)

Out of Scope (future)

  • MetaMask Snap package (canton_signTransaction RPC) -- enables the non-custodial flow for real browser users
  • dApp frontend / demo web UI -- custom UI that drives the prepare/sign/execute flow
  • Migrate custodial users to non-custodial (optional -- users can choose)

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions