Skip to content

About

A private institutional RFQ and atomic delivery-versus-payment venue, built on the Canton Network.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ShadowDesk

Treasury execution for Canton node operators, with limits the agent cannot break. Two approvers sign a mandate on the ledger, an agent sells within it through sealed quotes from approved desks, and both sides of each trade settle in one transaction.

Approve. Quote. Award. Settle.

Watch the ShadowDesk intro (30 seconds)

Watch the 30-second intro: 16:9 · 1:1 · 9:16. It is rendered from code in video/ with Remotion; the script is in docs/MOTION-SCRIPT.md.

Pitch deck: download the PDF (10 slides, October 2026).

Why it exists

Canton node operators and app teams earn Canton Coin but pay many of their costs in dollars, so part of that income has to be converted, regularly. Whoever runs the conversion, a person or a bot, holds the keys and has no hard limit on how much is sold, at what price, or to whom. When the limits live in the seller's own code or runbook, the party being limited is also the party enforcing the limit.

That is our working hypothesis, and we are testing it in conversations with operators (docs/POSITIONING.md). The engine is general; node operators are the first niche.

ShadowDesk moves the limits into Daml contracts that a second approver signs:

  • A TreasuryMandate, signed by the buyer and a risk officer who must be a different party, fixes the approved dealers, the asset, the settlement asset, the maximum amount, the maximum price and the expiry.
  • Every RFQ is co-signed by the risk officer, so OpenRfq on an approved mandate is the only way to open one. It refuses anything above the limits or to an unapproved dealer, and Deal.Settle checks the mandate again before anything moves.
  • A competing dealer never sees another dealer's quote, and the award follows a fixed rule recorded on the ledger.
  • Both settlement legs and the receipt commit in one transaction, or none of them do.

An earlier version let the buyer party create an RFQ outside any mandate and settle it. The contracts now close that, and runAgentCannotBypassMandate tries the bypass four ways. The fixed package runs on DevNet, where two rounds settled through it on 2026-10-25 (evidence).

Those rounds traded CBTC for BETH test tokens. Selling Canton Coin for a dollar stablecoin, the node operators' actual trade, is not built yet; docs/POSITIONING.md lists what runs today and what is next.

Where each claim is proven

Environment Topology Assets What it proves
LocalNet Two participants, one synchronizer Synthetic The full round, every rejection path, and that a losing dealer on a separate participant sees none of the winner's quotes
Canton DevNet (HackCanton-01) One participant hosting all four parties Fauceted CBTC and BETH through the Canton Token Standard registry Deal.Settle and both registry legs in one submitMany transaction

No single run proves cross-participant privacy and real-token settlement together. Every claim, its evidence and the wording to use are in docs/CLAIMS.md; the README, the deck and the video follow it.

Verify in 2 minutes

  1. Read the limits in the contract: OpenRfq in daml/src/ShadowDesk/Rfq.daml (lines 56-82) and the settlement re-check in daml/src/ShadowDesk/Settlement.daml (lines 22-44).
  2. Read the scenarios that test them in daml/src/ShadowDesk/Test.daml: runAgentCannotBypassMandate, runMandatePolicyLifecycle, runMaxPriceCannotBeOverridden, runMultiParticipantPrivacyAndSettlement, runRfqIsAwardedAtMostOnce and runDealMustMatchAward.
  3. Run them: cd daml && dpm test with Daml SDK 3.5.10 (about a minute after the SDK is installed).
  4. Watch a full recorded round: docs/demo-walkthrough/demo-walkthrough.mp4.
  5. Run the whole round yourself on LocalNet in one command: ./scripts/localnet/run-all.sh, then open http://localhost:3001.

The live dashboard at https://shadowdesk-inky.vercel.app/dashboard reads the DevNet participant and needs a HackCanton participant login to show ledger data. DevNet update ids for the 2026-10-25 rounds are in docs/evidence/devnet-2026-10-25-mandated.json. The dashboard's Run round goes through the worker, which still runs the earlier package until it is redeployed.

This project was built for the Canton Network track of HackCanton Season 3. It is a proof of concept, not a production service, and it has no users or pilots yet. Nothing here is financial advice, an investment product, or a custodial service. DevNet settlement uses fauceted test tokens (CBTC, BETH) minted by the BitSafe faucet, and LocalNet runs synthetic assets; no real money moves.

Table of Contents

Quickstart

To see the whole workflow in one command, bootstrap a clean LocalNet (ledger, package, four parties, and the agent round) and open the dashboard.

Terminal one:

./scripts/localnet/run-all.sh

Terminal two, if the dashboard did not start:

cd frontend
cp .env.localnet .env.local   # LocalNet parties and ports
npm install
npm run dev                   # dashboard on http://localhost:3001

Open http://localhost:3001. The dashboard renders the ledger state directly. A demo round runs during bootstrap, so the projections, the settlement receipt, and the registry legs are already populated.

To run a real DevNet round instead, set SHADOWDESK_NETWORK=devnet, configure the DevNet parties in frontend/.env.local, fund them once with npm run faucet:fund, then press Run round in the dashboard.

Screenshots

The dashboard empty state:

Shadow desk landing

The public metadata view of a run:

Public fabric terminal

Public privacy banner after the round:

Public privacy banner

The institutional buyer view:

Institutional buyer view

The settlement view with receipt and registry legs:

Institutional settlement

The console streaming agent output mid-round:

Console streaming

The console after a completed round:

Console complete

The public view after the round completed:

Public after round

Full recorded walkthrough:

Demo walkthrough video

The One Rule

Never claim a trade the ledger did not actually settle.

ShadowDesk's whole point is that execution is closed with real ledger and registry evidence, so every claim in its UI is grounded in a recorded transaction.

The rule is enforced in three layers.

First, settlement claims. A SettlementReceipt is only shown as settled when it is the response of a live Deal.Settle call and both registry Allocation_ExecuteTransfer legs committed in the same submitMany transaction. The receipt cannot exist unless the tokens moved, and the tokens cannot move unless the receipt was written (agents/shared/real-dvp.ts).

Second, award claims. The winning quote is the quote the seal actually referenced. The rank and the saving are re-derived from the recorded bid set, never from a hardcoded number. A claim that the cheaper bid won is verified from the ledger, and the dashboard re-derives it from ledger data alone.

Third, privacy claims. A quote is only "private" when the Daml contract's stakeholder set says so - the QuoteProposal is signatory to a single dealer and observed by the buyer. The losing dealer's all() over the winner's quotes returns empty, and that assertion is part of the scenario suite and an e2e check (daml/src/ShadowDesk/Test.daml, agents/e2e/real-flow-check.ts).

What ShadowDesk Does

Mandate approval A buyer and a risk officer approve a TreasuryMandate that fixes the dealers, the assets, the amount, the maximum price, and the expiry. Approval is a ledger event with both parties as signatories.

Private RFQ The buyer agent opens a BlockTradeRFQ from the approved mandate. OpenRfq is the mandate's control point: the ledger refuses any request that exceeds the cap amount, price or expiry, or invites an unapproved dealer. Every RFQ is co-signed by the risk officer, so the agent cannot create one any other way.

Sealed quoting Each dealer agent submits one QuoteProposal, signatory to that dealer alone and observed only by the buyer. A competing dealer never sees another dealer's price - that is a Daml stakeholder property, not a UI filter.

Deterministic award The buyer agent ranks the proposals with the LowestPriceThenBidId policy and seals the winning quote. Sealing consumes the RFQ, so a request yields at most one award, and the sealed quote records the mandate link, the winning bid id, and the rule it was awarded under.

Atomic settlement Both counterparties authorize the Deal against the sealed quote, then Settle executes delivery-versus-payment. On DevNet that is one ledger update: Deal.Settle and both registry Allocation_ExecuteTransfer commands commit in a single submitMany transaction.

Verified, then remembered The dashboard rebuilds state from ledger and registry queries, and the durable worker reruns real DevNet rounds on demand. Run history and receipts are queryable, not scrubbed.

Architecture

Two decisions drive this architecture.

The first is that policy lives in the Daml contracts, not in the agents. Cap enforcement (OpenRfq), award determinism (LowestPriceThenBidId), and atomic settlement (Deal.Settle) are ledger behavior, so no agent can exceed the mandate, re-award, or partially settle on its own.

The second is that settlement spans two packages in one update. The ShadowDesk receipt and the registry legs commit atomically, so the receipt is proof the tokens moved and the tokens only move with a receipt.

Phase What happens Where
Approve Buyer + risk officer approve a TreasuryMandate (dealers, assets, amount, max price, expiry). daml/src/ShadowDesk/Rfq.daml
Open Buyer opens a BlockTradeRFQ; OpenRfq refuses anything past the cap. daml/src/ShadowDesk/Rfq.daml
Quote Dealers submit sealed QuoteProposals, signatory to one dealer, observed by the buyer. daml/src/ShadowDesk/Rfq.daml, agents/dealer/dealer.ts
Award Buyer runs LowestPriceThenBidId and seals; sealing consumes the RFQ. daml/src/ShadowDesk/Rfq.daml, agents/buyer/buyer.ts
Settle Deal.Settle + both registry legs in one submitMany. daml/src/ShadowDesk/Settlement.daml, agents/shared/real-dvp.ts
Verify Dashboard re-derives state from ledger + registry; losing-dealer zero-view is asserted. frontend/app/api/state, agents/e2e/real-flow-check.ts

Component by Component

Layer Module Responsibility
Contracts daml/src/ShadowDesk/Asset.daml Asset holdings used as settlement legs on LocalNet.
Contracts daml/src/ShadowDesk/Rfq.daml TreasuryMandate, ApprovedMandate and the mandate lifecycle (approve, enforce caps).
Contracts daml/src/ShadowDesk/Rfq.daml OpenRfq, QuoteProposal, LowestPriceThenBidId, seal - the RFQ lifecycle.
Contracts daml/src/ShadowDesk/Settlement.daml Deal, Settle, SettlementReceipt - atomic DvP.
Contracts daml/src/ShadowDesk/Test.daml The eleven scenario suite: policy lifecycle, private RFQ, cap override, authz + replay, quote validation, atomic DvP, invalid leaves, multi-participant privacy, deal-must-match-award, at-most-once award, and the mandate bypass attempts.
Agent agents/shared/client.ts JSON Ledger API v2 client, submitMany, party-authenticated calls.
Agent agents/shared/config.ts Network, parties, instruments, caps from environment.
Agent agents/buyer/buyer.ts Opens RFQs, ranks sealed quotes, seals, drives settlement.
Agent agents/dealer/dealer.ts Submits QuoteProposals.
Agent agents/shared/settlement.ts V1 settlement against registry allocations.
Agent agents/shared/real-dvp.ts Atomic Deal.Settle + registry legs in one submitMany.
Agent agents/shared/settlement-v2.ts, agents/e2e/real-dvp-v2.ts, agents/e2e/v2-settlement-check.ts V2 registry-native receipt, validated locally.
Agent agents/e2e/real-guard-check.ts Rejection-path checks (cap, privacy, atomicity) against a live ledger.
Agent agents/e2e/real-flow-check.ts Full happy-path round incl. losing-dealer zero-view assertion.
Agent agents/e2e/faucet-fund.ts Mints fauceted CBTC/BETH for the settlement parties.
Agent agents/e2e/prune-stale.ts Cleans stale archived/expired contracts between rounds.
Worker worker/server.mjs Durable on-demand runner on the VPS; spawns agent runs, persists run state.
Frontend frontend/app/api/state Dashboard projection rebuilt live from ledger + registry.
Frontend frontend/app/api/replay Proxies Run round to the worker and streams agent output.
Frontend frontend/app/api/auth OIDC (Keycloak) login for the DevNet participant.
Frontend frontend/lib/state.ts Ledger + registry projection model.
Frontend frontend/lib/canton.ts Canton Ledger API queries from the dashboard.
Frontend frontend/lib/session-cookie.ts Signed session cookie for replay authorization.

Safety, Enforced in Code

Claim How it is enforced
Mandates cap what an agent may trade Every RFQ is co-signed by a distinct risk officer, so it can only come from OpenRfq, which rejects anything past the mandate amount, max price or expiry, or to an unapproved dealer; settlement re-checks the mandate. Covered by runAgentCannotBypassMandate and runMaxPriceCannotBeOverridden, and asserted live by e2e:real-guard.
Quotes stay private QuoteProposal is signatory to one dealer, observed by the buyer; a rival dealer's all() returns empty.
One RFQ, at most one award Sealing consumes the RFQ; runRfqIsAwardedAtMostOnce.
Award matches the deal Deal authorization requires the sealed quote; runDealMustMatchAward.
Settlement is atomic Receipt + both registry legs in one submitMany; runAtomicDvPSettlement, runDvPRejectsInvalidLeaves, asserted live on DevNet by e2e:real-flow.
Secrets stay server-side Canton tokens, session files, and the worker secret are read from the environment and never sent to the client.

How ShadowDesk Uses Canton

Writes (via the JSON Ledger API v2, agents/shared/client.ts):

  • Mandate approval with buyer + risk officer signatories.
  • OpenRfq from the approved mandate.
  • Dealer QuoteProposals.
  • Seal / award under LowestPriceThenBidId.
  • Deal.Settle plus two registry Allocation_ExecuteTransfer legs as one submitMany.

Reads:

  • Party-authenticated contract queries (buyer sees its RFQs and sealed quotes; a dealer sees only its own quotes).
  • Token Standard registry state through the Digital Asset utilities endpoint (SHADOWDESK_REGISTRY_URL).

Contract: shadowdesk-treasury-mandated 1.0.0 (package id 941cafd1… on Daml SDK 3.5.10), deployed to the HackCanton DevNet participant and to the LocalNet sandbox. The earlier shadowdesk-treasury 1.0.0 (package id 50a21ee1…) is still vetted on DevNet; the worker behind the dashboard's Run round uses it until it is redeployed (docs/DEVNET-UPGRADE.md).

There is no custody at the app layer. The agent only submits transactions as its party; it never holds keys for anyone else. On DevNet the settlement tokens (CBTC, BETH) come from the BitSafe faucet, which mints them for the configured settlement parties. The utility registry tracks the allocations; the ledger holds the authored contracts.

Engineering Decisions and the Hard Problems

Privacy is a protocol primitive, not an app layer. On a public chain, a "private" RFQ usually means the app hides data that the platform still sees. On Canton, visibility is decided by Daml stakeholders, so a dealer's quote literally does not exist for a rival dealer. That claim is asserted in runMultiParticipantPrivacyAndSettlement and in the live e2e check, which prints the losing dealer's empty view.

Policy in the contract, not the agent. If the cap lived in the agent, a buggy or malicious agent could trade more than approved. OpenRfq and Deal enforce the mandate and the award in the ledger. The risk officer co-signs every BlockTradeRFQ, so the buyer party cannot create one on its own, and a mandate whose risk officer is the buyer is refused. Settlement fetches the mandate again, so even an award the buyer and a dealer sign between themselves cannot settle outside it. The earlier package signed RFQs with the buyer alone and made the mandate optional, which let the buyer settle outside it.

One atomic update spans two packages. The most fraudulent settlement would have the receipt exist while the tokens did not move. By committing Deal.Settle and both registry legs as one submitMany, the receipt is unilaterally tied to the transfers. If any leg fails, all fail.

Deterministic award beats discretion. LowestPriceThenBidId removes the "best execution" argument from the agent. The seal records the rule, the mandate link, and the winning bid, so an award is auditable as a ledger fact rather than as an agent's opinion. Best execution is evidenced, not (yet) enforced - the buyer could in principle seal a worse bid, and the runner asserts the recorded ranking instead.

The environments are honest about their split. LocalNet runs two participants, so runMultiParticipantPrivacyAndSettlement and the live privacy assertion run on a real participant boundary - but with synthetic assets. The DevNet run settles real fauceted CBTC and BETH, but against a single participant, so the same round does not cross the participant boundary. Both claims are made where the evidence is.

What Is Real vs Pending - The Honesty Table

Capability Status
Mandate lifecycle (approve, caps, expiry) Real - scenario suite + live DevNet/LocalNet rounds.
Cap enforcement on OpenRfq Real - runMaxPriceCannotBeOverridden + e2e:real-guard rejects an over-cap RFQ.
Every trade forced through a mandate Real - refused four ways in runAgentCannotBypassMandate; the enforcing package runs on DevNet and LocalNet, with two DevNet rounds settled through it on 2026-10-25. The earlier package let the buyer settle 5,000,000 at 149.0 against a mandate of 1,000,000 at 101.0; the dashboard worker still uses it until redeployed.
Private sealed quotes Real - QuoteProposal signatory to one dealer; rival all() empty.
Deterministic award Real - LowestPriceThenBidId, seal consumes the RFQ.
Atomic DvP in one update Real - DevNet 2026-10-25 on the mandated package: Deal.Settle + both registry legs in one submitMany, update ids in docs/evidence/devnet-2026-10-25-mandated.json.
Cross-participant privacy proof LocalNet only - two participants on LocalNet; the DevNet run uses one participant, so the privacy assertion runs against the LocalNet boundary.
V2 registry-native receipt LocalNet only - compiles and validates locally, but upload to the DevNet participant returns 403 (uploads need operator credentials), so DevNet settles via the V1 path.
Best-execution enforced on-ledger Not enforced - recorded and evidenced from the bid set; the runner asserts the ranking, the ledger does not force it.
Third dealer / RFQ amendment / cumulative mandate spend Roadmap - documented as next steps, not shipped. OpenRfq is nonconsuming, so each RFQ is capped but the total across RFQs is not.
Mainnet / real funds Not done - DevNet faucet tokens and synthetic LocalNet assets only.

The most honest limit is environment topology. Privacy across a participant boundary is proven on LocalNet; settlement on real registry tokens is proven on DevNet; no single run currently demonstrates both at once.

Validation

Check Command Covers
Daml scenario suite dpm test (in daml/) The eleven scenarios + noop on a clean sandbox.
Agent typecheck npm run typecheck (in agents/) TypeScript across buyer, dealer, shared, e2e, demo.
Agent live guard checks npm run e2e:real-guard Rejection paths against a running local ledger.
Agent live happy path npm run e2e:real-flow Full round incl. losing-dealer privacy assertion.
Award chain npm run e2e:award-chain Deal necessarily follows the sealed award.
DevNet funds npm run faucet:fund Mints fauceted CBTC/BETH for the settlement parties.
Frontend typecheck npm run typecheck (in frontend/) Next.js + React types.
Frontend build npm run build (in frontend/) Production Next.js build.
Frontend unit tests npm test (in frontend/) Registry projection + session cookie.
LocalNet smoke ./scripts/localnet/run-all.sh Sandbox, package deploy, four parties, agent round, dashboard.

There is no unit-test suite in the Daml style beyond the scenario suite; the commands above are the executable validation shipped with the repo.

Run It Locally

  1. Prerequisites

Node.js 20+, the dpm CLI, and the Daml SDK 3.5.10. On macOS, ensure ~/.dpm/bin is on PATH (./scripts/verify.sh checks the toolchain).

  1. LocalNet (the whole round in one command)
./scripts/localnet/run-all.sh

This boots a clean sandbox, deploys shadowdesk-treasury-mandated, provisions the two LocalNet participants, runs the agent round against synthetic assets, and serves the dashboard on http://localhost:3001.

  1. Manual LocalNet
./scripts/localnet/start-ledger.sh
cp frontend/.env.localnet frontend/.env.local
cd agents && npm install
npm run e2e:real-guard   # rejection paths
npm run e2e:real-flow    # full round
cd ../frontend && npm install && npm run dev
  1. DevNet
cp frontend/.env.devnet frontend/.env.local

Fill in the party IDs (buyer, risk officer, dealer A, dealer B - create them in the NODERS console when on DevNet) and set SHADOWDESK_NETWORK=devnet, the participant JSON URLs, and the OIDC endpoints for the HackCanton-01 participant. Then fund and run:

cd agents && npm install
npm run faucet:fund      # mints fauceted CBTC/BETH for the settlement parties
npm run e2e:real-flow    # real round on DevNet
cd ../frontend && npm install && npm run dev   # dashboard on :3001

The DevNet participant requires OIDC access tokens; SHADOWDESK_CANTON_ACCESS_TOKEN, SHADOWDESK_CANTON_REFRESH_TOKEN, and the session store are configured in frontend/.env.local and never committed.

The One-Flow Demo

Open the dashboard (http://localhost:3001, or, with a HackCanton participant login, press Run round on the live DevNet dashboard).

  • The mandate is approved: buyer + risk officer commit a TreasuryMandate with dealers, assets, amount, max price, and expiry.
  • The buyer opens a BlockTradeRFQ; OpenRfq accepts the in-cap request and would refuse an over-cap one.
  • Each dealer submits one QuoteProposal; the buyer observes the sealed bid set.
  • The buyer seals the winner under LowestPriceThenBidId; the RFQ is consumed.
  • Both sides authorize the Deal; Settle runs Deal.Settle plus both registry Allocation_ExecuteTransfer legs in one submitMany.
  • The dashboard projection updates: the receipt hash, the two registry legs, the rank, and the recorded saving appear - all re-derived from ledger and registry data.
  • The losing dealer's view shows zero of the winning quotes (asserted in the console output).

Open the demo walkthrough video for a full recorded run: docs/demo-walkthrough/demo-walkthrough.mp4.

Configuration

Variable Purpose
SHADOWDESK_NETWORK localnet (two participants) or devnet (HackCanton-01 participant).
SHADOWDESK_PARTICIPANT1_URL / SHADOWDESK_PARTICIPANT2_URL JSON Ledger API v2 endpoints for each participant.
SHADOWDESK_BUYER_PARTY / SHADOWDESK_RISK_OFFICER_PARTY / SHADOWDESK_DEALER_A_PARTY / SHADOWDESK_DEALER_B_PARTY The four role parties.
SHADOWDESK_ASSET_TO_BUY / SHADOWDESK_SETTLEMENT_ASSET Instrument pair (DevNet faucet mints CBTC/BETH).
SHADOWDESK_AMOUNT / SHADOWDESK_MAX_PRICE The trade cap the mandate and RFQ accept.
SHADOWDESK_REGISTRY_URL Canton Token Standard utility registry endpoint.
SHADOWDESK_FAUCET_API_URL BitSafe DevNet faucet; mints CBTC/BETH for the settlement parties.
SHADOWDESK_CANTON_ACCESS_TOKEN / SHADOWDESK_CANTON_REFRESH_TOKEN / SHADOWDESK_LEDGER_USER_ID DevNet OIDC credentials. Never commit.
SHADOWDESK_OIDC_* Keycloak issuer, authorization, token, userinfo, logout, revocation, client id, redirect, scopes.
SHADOWDESK_AUTH_SESSION_DIR / SHADOWDESK_AUTH_COOKIE_SECURE Signed session cookie for replay authorization.
SHADOWDESK_WORKER_URL / SHADOWDESK_WORKER_SECRET Vercel /api/replay → VPS worker proxy credentials. Never commit.

Never commit .env.local files, Canton access tokens, session files, or the worker secret.

Deploy

Local production build

cd frontend
npm run build
npm run start

Live dashboard (Vercel)

The repo root deploys the Next.js application from frontend/ to Vercel (Hobby plan). The dashboard is live at https://shadowdesk-inky.vercel.app with the institutional view at /dashboard. NEXT_DIST_DIR keeps the dev and production build directories separate, so next dev and next build never corrupt each other's page manifest.

The deployed /api/replay route does not run rounds itself. Vercel Hobby caps a single request at 300 seconds, so the route proxies to the durable worker: it POSTs /v1/runs to the worker, streams the run's log lines while the request is alive, and points long runs at the worker's job-status endpoint.

Durable worker (VPS)

The worker (worker/server.mjs) runs on the VPS bound to 127.0.0.1:8787, not exposed externally. It spawns the agent scripts on the real DevNet environment, holds a single-round lock, and persists run state. The dashboard's Run round reaches it through SHADOWDESK_WORKER_URL / SHADOWDESK_WORKER_SECRET in the Vercel environment.

To update a deployed worker: sync the agents and worker to the VPS, restart the worker service, then run a fresh round from the dashboard.

Project Layout

ShadowDesk/
├─ daml/                         Daml contracts + scenario suite
│  ├─ src/ShadowDesk/            Asset (mandate), Rfq, Settlement, Test
│  └─ daml.yaml                  shadowdesk-treasury-mandated 1.0.0, SDK 3.5.10
├─ agents/                       Canton agents
│  ├─ buyer/  dealer/            Role agents (RFQ, quotes, seal, settle)
│  ├─ shared/                    client, config, faucet, settlement, real-dvp
│  ├─ e2e/                       real-flow, real-guard, award-chain, faucet-fund, prune-stale
│  ├─ demo/                      LocalNet and DevNet demo scripts
│  └─ package.json               typecheck, e2e:* commands
├─ worker/                       durable on-demand runner (server.mjs)
├─ frontend/                     Next.js dashboard
│  ├─ app/api/                   state, replay, auth (login/callback/logout/session)
│  ├─ lib/                       canton, state, registry-projection, session-cookie
│  ├─ test/                      registry projection + session cookie tests
│  └─ .env.devnet / .env.localnet / .env.example
├─ scripts/                      verify.sh, localnet/{start-ledger,run-demo,run-all,smoke-test}
└─ docs/                         architecture, business brief, pilot plan, demo-walkthrough

log/ holds per-run ledger and agent transcripts. Review them before changing settlement or RFQ behavior.

Tech Stack, Credits, Roadmap

Tech stack

Daml Smart Contracts (SDK 3.5.10) on Canton, the JSON Ledger API v2, TypeScript agents (tsx), the Canton Token Standard registry, the BitSafe DevNet faucet, Next.js 15 (App Router) with React 19, Tailwind CSS 4, motion, and lucide-react. Node's built-in test runner covers the frontend projection and session cookie modules.

Credits

Built on the Canton Network DevNet (HackCanton-01), with fauceted tokens from BitSafe and registry queries through the Digital Asset utilities endpoint. The Daml scenario suite and the JSON Ledger API v2 make the on-ledger guarantees checkable end to end.

Roadmap

  • Redeploy the worker on the VPS so the dashboard's Run round uses the mandated package (docs/DEVNET-UPGRADE.md).
  • Deploy the V2 registry-native settlement package once DevNet uploads are enabled (today the V1 settle path runs on DevNet).
  • Run a DevNet round across a real participant boundary so cross-participant privacy and real registry tokens appear in one run.
  • Add a third dealer, RFQ amendment, and cumulative mandate-spend enforcement.
  • Make best execution an on-ledger guarantee rather than a recorded-and-asserted ranking.

Disclaimer and License

ShadowDesk is a hackathon proof of concept. It is not a production treasury system, a financial product, a broker-dealer service, or a custodial service, and it accepts no liability for trades placed through it or for the behavior of third-party networks it calls. DevNet settlement uses fauceted test tokens (CBTC, BETH) that move no real value; LocalNet runs synthetic assets. This repository moves no real funds.

License: MIT.

About

A private institutional RFQ and atomic delivery-versus-payment venue, built on the Canton Network.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages