Backend integrators: your validator, your party, your key
Treasury desks, bots, and services execute Synfin swaps from Node with no browser and no wallet vendor: your party lives on YOUR participant, your key signs every write, and Synfin never holds either. Quote and plan are the same keyed REST calls as everyone else; execution rides the same published adapter seam.
The model: self-hosted
You run your own Canton validator and your external party lives on your participant. This is the supported and documented path. Synfin does not offer hosted parties for now: hosting your party would put us in a custody-adjacent role we deliberately avoid, and self-hosting is better for you anyway; among other things you control the packages on your own participant (see atomic fees below). If a lighter tier ever exists it will be announced; nothing is promised today.
Participant requirements
-
A Canton validator on mainnet with the JSON Ledger API v2 reachable by
your backend, and a ledger API user your backend authenticates as (the
userIdin every submission; splice validators commonly useadministrator). -
An external party allocated on your participant whose Ed25519 key YOUR
backend holds. Allocation is a one-time operation:
/v2/parties/external/generate-topology, sign the returned multi-hash with your key, then/v2/parties/external/allocate. Your validator tooling or the Canton wallet SDK can do the same; the raw endpoints are what our own mainnet canaries used. - Hard requirement: vet the registrar's packages for every asset you
intend to receive, BEFORE any swap. Every token is implemented by its registrar's Daml packages (USDCx: Digital
Asset's Utility packages,
utility-registry-app-v0,utility-registry-holding-v0and their dependencies, at the versions the registrar currently runs). Canton requires the RECEIVING party's participant to have vetted those packages, so a venue cannot even submit a payout to a party hosted on a participant that lacks them. That failure is INVISIBLE to the receiver: the submission is rejected on the venue's side, nothing is sequenced, nothing reaches your participant or any explorer, and your deposit stays accepted while the payout never comes. Measured on our own mainnet validator: three accepted deposits were never paid for exactly this reason (docs/canary/2026-09-17-tradecraft-payout-package-gap.md). Check before your first swap:POST /v2/interactive-submission/preferred-packageson your participant for each output package name must resolve, not returnPACKAGE_NAMES_NOT_FOUND. Obtain the packages from the registrar's publisher, under their terms; they are node artifacts, not something to commit to your repository. - Receive preapprovals for payout assets. Hold one where the asset's registrar offers a creation path. How a venue delivers to a receiver WITHOUT one is not yet measured for a self-custodied party: the 2026-07-22 run that appeared to show "no delivery without a preapproval" is confounded by the missing packages above, and a receiver without a USDCx preapproval, hosted on a participant that HAS the packages, was paid as a two-step offer on 2026-07-16. Until re-measured on a vetted participant, treat pairs whose output you cannot preapprove as not-yet-receivable.
- Optional, your choice: the CIP-0112 batching package
(
splice-util-token-standard-wallet1.1.0 or later) uploaded to your participant. Without it everything works and fee collection degrades fee-less, exactly like the browser wallets today. With it, when fee collection turns on, your swaps can carry the deposit and the fee in ONE atomic transaction (the standing-utility pattern below). Because you control your own participant, you do not wait for any wallet vendor to install it: backend integrators can be the first segment where atomic fee collection works day one.
One rule the platform enforces: single root
Canton rejects multi-root transactions for external parties (proven on
mainnet, twice). The backend signer therefore exposes no way to submit more
than one command: every submission is exactly one exercise. Batching is done
the standard way instead: create a standing
BatchingUtility once (one transaction), then each batch is ONE exercise
of it carrying multiple token-standard actions inside. Same seam, same single
root.
The code: one signer, the same adapter
import { createClient, executePlan, track } from '@synfin/client';
import { createCip0103WalletAdapter, MAINNET_REGISTRY_BASE_URL } from '@synfin/wallet-partylayer/cip0103';
import { createBackendSigner } from '@synfin/wallet-partylayer/backend';
import { readFileSync } from 'node:fs';
const signer = createBackendSigner({
participant: {
baseUrl: process.env.PARTICIPANT_JSON_API, // your validator
token: process.env.PARTICIPANT_TOKEN, // your ledger API auth
userId: 'administrator',
},
party: process.env.BACKEND_PARTY, // hint::fingerprint
privateKey: readFileSync('/secure/party-key.pem', 'utf8'),
});
const wallet = createCip0103WalletAdapter({
ledger: { baseUrl: process.env.PARTICIPANT_JSON_API, token: process.env.PARTICIPANT_TOKEN },
registry: { baseUrl: MAINNET_REGISTRY_BASE_URL, token: '' },
signer,
instrumentAdmin,
});
// quote -> plan (keyed REST) -> execute -> track, unchanged from every
// other integrator:
const synfin = createClient({ apiKey: process.env.SYNFIN_API_KEY });
const plan = await synfin.createPlan({ /* ... */ });
const result = await executePlan({ plan, wallet });
Under the hood the signer does exactly what our mainnet canaries proved:
prepare on your participant, a raw Ed25519 signature over the prepared-transaction
hash with your key, execute, and the update id resolved
honestly from your own update stream. Node 18 or later; no dependencies
beyond the package.
Key custody, plainly
-
The key is a PKCS8 PEM your backend loads itself (file with tight
permissions, or wrapped by your KMS and decrypted into memory). The signer
accepts the PEM string or a Node
KeyObject; it never logs it, never puts it in an error, never sends it anywhere. - Never pass keys or tokens as command-line arguments (they leak via the process list) and never commit them. Environment variables or files with restricted permissions.
-
The key signs only what your process asked Synfin to prepare: a deposit
your plan specified, bounded by the plan's on-ledger
minReceiveand deadline. Synfin builds unsigned transactions and never sees the key.
Support status, honestly
Answer-as-tested, like every wallet class: the backend row ships with
execute: false until a real funded mainnet swap through the PUBLISHED
packages cites its update id. The signer's request wire shapes are pinned to
our mainnet-proven canary flow and fully covered by automated tests up to the
signing boundary; the funded proof run is a supervised, recorded release step.
Want to be the first backend integration proven live? Write
info@cayvox.com.