
Canton Invoice
One Invoice, Four Views
About
The problem it solves In traditional invoicing, every party in the supply chain — the seller, buyer, logistics carrier, and bookkeeper — typically sees the exact same document. This creates a serious privacy problem: your shipping carrier knows your profit margins, your accountant sees customer delivery addresses, and your buyer sees your internal cost structure. Businesses either accept this data leakage or resort to manually creating separate documents for each party — an error-prone, time-consuming process. Canton Invoice solves this with structurally enforced privacy. When a seller creates a single invoice, the system automatically generates four distinct, role-appropriate views: 🏷️ Seller — Full invoice with line items, pricing, payment controls, and the ability to share views 💳 Buyer — Payment-focused view with amounts due, payment status, and wallet integration 🚚 Carrier — Logistics-only view with shipping addresses, item names, quantities, and delivery terms — no prices, discounts, or tax data exist in this view at all 📊 Bookkeeper — Financial summary with totals, tax breakdowns, and invoice status — no addresses, contacts, or line item details This isn't just app-level filtering where sensitive data is hidden by the UI. Each view is a separate Daml contract on the Canton Network where restricted fields are structurally absent from the template. Even if a carrier's node is compromised, price data cannot be recovered — because it was never there. Real-world use cases: A manufacturer shares logistics details with a 3PL provider without exposing wholesale pricing A CFO shares financial records with an external auditor without leaking customer PII or shipping details A buyer sees only what they owe and pays directly through Canton wallet integration Canton Invoice also integrates with the Splice Token Standard for native on-ledger payments using Canton Coin, with AllocationRequest-based settlement that creates immutable PaymentReceipt contracts as proof of payment. Use of AI tools and agents We used AI coding assistants (Claude/Antigravity) heavily as a pair-programming and debugging partner, especially during implementation and DevOps Debugging & Execution Acceleration (AI): AI was most valuable for unblocking us during development by: • Diagnosing infra failures: reading Docker + Canton logs line-by-line, isolating TLS/cert and synchronizer connectivity failures, and helping confirm whether issues were on the sequencer side vs. our local configuration. • Guiding fixes with runnable steps: generating exact commands and configs (e.g., updating startup scripts, fetching migration identifiers, adjusting environment variables), and iterating quickly when errors changed. • Rapid code execution + iteration: proposing small, testable patches, then helping us validate behavior through repeated run → observe → fix cycles (especially for node connectivity, port conflicts, and Ledger API integration).
How it's made
Challenges we ran into 1. DevNet Connectivity Issues Our biggest operational hurdle was connecting our validator node to the Canton Global Synchronizer. After deploying on DevNet3, we found that multiple sequencer nodes (Tradeweb-Markets-1, Cumberland-1, Cumberland-2) were either unreachable or returning TLS certificate mismatches (ingress.local instead of the expected domain). This produced persistent SYNC_SERVICE_BAD_CONNECTIVITY errors for hours. Since these were infrastructure issues outside our control, we had to migrate to DevNet5 — which required understanding the full start.sh bootstrapping flow, debugging port conflicts with other teams on the shared machine, and re-onboarding with fresh credentials. 2. Designing Structural Privacy in Daml Getting the disclosure model right was non-trivial. The key insight was that privacy should not be enforced at the API or UI layer — it needs to be baked into the Daml template structure itself. We created separate LogisticsView and BookkeeperView templates that are deliberately missing sensitive fields (e.g., LogisticsItem has no unitPrice, discount, or taxRate fields, unlike LineItem). The Invoice_ShareWithCarrier choice maps LineItem → LogisticsItem, stripping price data during the transformation. This required careful type design to ensure each view was self-contained and correct. 3. Multi-Role Authentication on LocalNet Supporting five distinct roles (seller, buyer, carrier, bookkeeper, app-provider) with proper party isolation on a single LocalNet instance required wiring up the participant node's ledger user system carefully. Each role maps to a different Daml party, and the DashboardRouter dynamically renders the correct dashboard based on the logged-in user's identity. Getting the splice-onboarding module to allocate and wire up the logistics and finance parties with correct ReadAs/ActAs rights took significant iteration. 4. Real-Time Dashboard Updates Each dashboard polls its respective contracts (invoices, logistics views, bookkeeper views) at 5-second intervals, but ensuring the frontend correctly reflected the state transitions — especially when a seller marks an invoice as paid and it auto-shares views with carrier and bookkeeper before archiving — required careful sequencing of nonconsuming and consuming Daml choices.