Canton-native business banking where role privacy is enforced by the ledger, not by the UI.
FinHive is a multi-user business workspace for managing money: invoices, payments, payroll, recurring revenue, treasury. It is built on Canton because role-scoped visibility is enforced cryptographically by the contract, not by application-level access control.
The CEO sees the budget envelope. The bookkeeper sees the line items. The vendor sees only their own invoices. Not because we built a filter, but because Canton's stakeholder model makes any other view impossible.
Mercury, Brex, Ramp, Bill.com. Every SMB finance tool today is built admin-down: the org admin sees everything, and roles are access-control layers on top. That model leaks salary data through bookkeepers, leaks contractor invoices through the AP queue, and leaks vendor pricing through finance dashboards. You cannot fix it with permissions. The data model is wrong.
Canton inverts the model. signatory, observer and controller are first-class contract primitives. The CEO is never a stakeholder in the line items of an AP clerk's invoice, so the synchronizer never sends that data to the CEO's participant. Privacy is the default. Visibility is the exception you have to opt into.
FinHive ships this as the operating account for teams of 5 to 50 people whose treasury includes stablecoins and whose CEO does not need to see every line item.
A single host runs the whole stack. Auth0 and the LLM provider are the only external dependencies.
| Service | Port | Role |
|---|---|---|
| Host Caddy | 443 |
TLS terminator, wildcard *.unitynodes.com on a Cloudflare origin certificate |
| Next.js 16 (App Router) | 3100 |
Role-switching workspace, server actions, server components |
| Node AP agent | internal | Polls invoices, asks an LLM for a verdict, signs proposals on chain |
| Canton 3.5.1 validator (Splice 0.6.5) | 7575 |
JSON Ledger API v2, Participant, ANS |
| Service | Role |
|---|---|
| Auth0 M2M | client_credentials grant, audience https://canton.network.global |
| LLM provider | Any OpenAI-compatible endpoint. Groq Llama 3.3 70B by default; OpenRouter, OpenAI, Anthropic also work |
- Browser hits
finhive.unitynodes.com. Host Caddy terminates TLS. - Caddy forwards to the Next.js app on
127.0.0.1:3100. - The web app calls the Canton JSON Ledger API on
127.0.0.1:7575and Auth0 for M2M tokens. - The AP agent polls the same JSON API, calls the LLM for verdicts, and signs proposals back to the validator.
flowchart LR
user([User browser])
subgraph host[Single host]
direction TB
caddy["Host Caddy<br/>wildcard TLS"]
web["Next.js 16 App Router<br/>port 3100"]
agent["Node AP agent<br/>poller"]
validator["Canton 3.5.1 validator<br/>Splice 0.6.5<br/>JSON Ledger API v2<br/>port 7575"]
end
auth0[("Auth0 M2M")]
llm[("LLM provider")]
user --> caddy --> web
web --> validator
web --> auth0
agent --> validator
agent --> auth0
agent --> llm
Not deployed: PQS, Postgres, Keycloak, Daml trigger runner.
See docs/architecture.md for the full breakdown.
| Template | Signatories | Purpose |
|---|---|---|
Company |
operator |
Tenant root |
Role |
operator |
Maps a party to a role: CEO, AP_Clerk, Vendor, HR or Employee |
Invoice |
vendor, operator |
AP invoice. AP_Clerk is observer. CEO is not a stakeholder |
BudgetView |
apClerk |
Aggregate view, total only, no line items. CEO is observer |
Settlement |
apClerk |
Created on ApproveInvoice. Emits FeaturedAppActivityMarker |
PaymentPolicy |
ceo |
Auto-approval rules: limits, vendor allowlist, frequency caps |
AgentSpendingLimit |
ceo |
Cryptographic cap on the AI agent's autonomous spend |
RecurringPayment |
customer, vendor, operator |
AR and subscription primitive |
operator is a co-signatory of Invoice and RecurringPayment so it can create FeaturedAppActivityMarker (which requires operator as signatory) in the same transaction. FeaturedAppActivityMarker is emitted on Invoice.ApproveInvoice and RecurringPayment.Charge, the two rewardable choices for Featured App readiness.
Sources in daml/FinHive/.
A Node and TypeScript poller in agent/ watches for new Invoice contracts in Pending status via the JSON Ledger API v2. It uses any OpenAI-compatible provider, configured through env (LLM_BASE_URL, LLM_API_KEY, LLM_MODEL). Default is Groq Llama 3.3 70B; OpenRouter, OpenAI and Anthropic-compatible endpoints all work.
For each pending invoice the agent:
- Reads the company's
PaymentPolicyandAgentSpendingLimit. - Asks the configured LLM for a verdict given the policy, the invoice and the remaining limit.
- Falls back to a deterministic rule if the LLM is unavailable.
- If the verdict is
AUTO_APPROVEand the amount fits the spending limit, exercisesApproveInvoicedirectly. TheConsumeBudgetchoice asserts the cap on the ledger. - Otherwise creates a
ProposedActioncontract that the AP_Clerk sees in their inbox with accept and override buttons.
The agent cannot exceed the cryptographically enforced limit. Even a misbehaving LLM cannot move money beyond what the CEO signed off on.
- A running Canton 3.5.1 validator (Splice 0.6.5)
- Daml SDK 3.4.11 (
sdk-version: 3.4.11indaml.yaml) - Node.js 20 or later, and pnpm 9
- Docker Compose 2.26 or later
- An Auth0 tenant with an M2M application (
client_credentialsgrant, audiencehttps://canton.network.global, scopedaml_ledger_api) - An OpenAI-compatible LLM API key (default Groq, or OpenRouter, OpenAI, Anthropic-compatible)
git clone https://github.com/UnityNodes/FinHive.git
cd FinHive
# 1. Install the toolchain (Daml SDK, Java, Node 20, pnpm)
bash scripts/bootstrap-toolchain.sh
# 2. Configure environment
cp .env.example .env
# Edit .env: Auth0 M2M credentials, LLM API key, ledger URL
# 3. Build and test the Daml model
daml build
cd daml-tests && daml test && cd ..
# 4. Upload the DAR to the running validator (writes the package id to .env)
bash scripts/upload-dar.sh
# 5. Allocate the FinHive parties and grant the backend act-as rights
bash scripts/setup-ledger.sh
# 6. (optional) Seed demo data
python3 scripts/seed.py
# 7. Start the web app and agent via Docker Compose
docker compose -f infra/docker-compose.yml up -d
# 8. Route a subdomain through Caddy with TLS
# Append infra/caddy-finhive.snippet to your Caddyfile, then:
sudo systemctl reload caddy
# and add a DNS A record for your subdomain to the serverOpen the deployed URL, or http://localhost:3100 for local dev. Use the role switcher in the header to move between the Vendor, AP_Clerk, CEO, HR and AR views.
- Vendor creates a $5,000 invoice.
- Switch to AP_Clerk. Full line items visible, AI proposal shown.
- AP_Clerk approves.
Settlementis created andFeaturedAppActivityMarkeris emitted on the ledger. - Switch to CEO. The dashboard shows
Vendor X paid $5,000as aBudgetViewaggregate. Line items: not visible. This is not a UI filter. The Canton synchronizer never sendsInvoicecontracts to a party that is not a stakeholder.
- Live workspace: finhive.unitynodes.com
- Slide deck: finhive.unitynodes.com/presentation
The slide deck walks through the privacy thesis, the role split, the AI assistant and the stack. Use the right and left arrow keys, or click the bottom dots to jump.
- Phase 0, now. MVP on DevNet. Six Daml templates, multi-role app, AI agent, deployed.
- Phase 1. TestNet to MainNet, multi-participant privacy, real settlement.
- Phase 2. Scale and multi-jurisdiction.
Apache 2.0. See LICENSE.
Daml templates, frontend, agent and infra config are all open source. The point is composability. If you want to build your own SMB banking product on Canton, fork the templates.
Built on the Canton Network and the open-source Splice reference implementation.