Quickstart
Install one package, get a key, quote a swap in minutes, and execute one from your own Canton wallet within the hour. Every snippet here matches the runnable example at synfin-examples/quickstart-node, so you can clone it and run against the live API.
1. Install
@synfin/client (zero runtime dependencies, Node 18+ or any browser)
covers quote and plan. To EXECUTE a swap you also add the reference wallet adapter
and its PartyLayer peer (step 4):
npm install @synfin/client
npm install @synfin/wallet-partylayer @partylayer/sdk # for execute (step 4)
Do not install @synfin/spec,
@synfin/adapters, @synfin/router-ref,
@synfin/conformance, or @synfin/cli, those are
deprecated. Synfin is a hosted API;
@synfin/client is how you call it (and
@synfin/widget is the optional drop-in UI).
2. Get a key
Create a free key at portal.synfin.xyz. It carries your rate limit and your fee configuration, and is shown once at creation. Design partners can also reach a human at info@cayvox.com. Keep the key server-side; in a browser widget use a dedicated widget key.
3. First quote (minutes)
A keyed quote returns each venue's net receive, ranked best first, plus your
disclosed clientFees.
Tradecraft is the executable venue today; the other venues
appear for comparison but are not yet plan-buildable.
import { createClient } from '@synfin/client';
const synfin = createClient({ apiKey: process.env.SYNFIN_API_KEY });
const quote = await synfin.getQuote({
from: 'CC',
to: 'USDCx',
amount: '100',
feeBps: 30, // your integrator fee (0 to the cap)
feeRecipient: 'yourparty::1220...', // where your fee would settle
});
const best = quote.venues.find((v) => v.available);
console.log(best.venueId, best.net, best.clientFees?.userReceives); curl "https://synfin.xyz/api/quote?from=CC&to=USDCx&amount=100&feeBps=30&feeRecipient=yourparty::1220..." \
-H "x-api-key: sk_live_..."
Read the response like this: gross is a venue's headline output;
net is what arrives after the venue's network fee;
best is the highest net. A venue that cannot quote stays in the
list as available: false with a rejectionCode.
Note
CC resolves to its registry name Amulet in responses.
The full response shape is in the
API reference.
4. First swap (within the hour)
Three paths from here, one seam. A dApp connecting a USER'S wallet follows the PartyLayer path below. A HOSTED wallet (you are the wallet: the party is yours and you sign natively) takes the hosted-wallet path. A BACKEND with its own validator and self-custodied key (treasury desks, bots) takes the backend path. Both non-browser paths rejoin here for the plan-and-track calls, which are identical.
Ask for a one-call plan for the venue you chose. The server computes the
memo floor (minReceive) and pins the quote, you never do. Then
execute it from your own wallet. If you use a PartyLayer
wallet (Loop and others), the reference adapter
@synfin/wallet-partylayer gives you a ready WalletAdapter from just the connected client
and the taker party, no network identifiers to paste.
executePlan drives it through the plan; Synfin holds no keys. (Where
partyLayerClient comes from:
connect a wallet.)
import { executePlan, track, isPartnerTerminal } from '@synfin/client';
import { createPartyLayerWalletAdapter } from '@synfin/wallet-partylayer';
const plan = await synfin.createPlan({
from: 'CC', to: 'USDCx', amount: '100',
venueId: best.venueId,
takerParty: 'yourparty::1220...',
idempotencyKey: crypto.randomUUID(), // retries return the SAME plan
});
// minReceive rides the deposit memo and is enforced ON-LEDGER: a bad quote
// aborts and your funds never leave the wallet.
console.log(plan.quoteRef.minReceive, plan.collectsFees, plan.steps.map(s => s.kind));
// Wallet in, adapter out: no CIP-56 code, no ledger reads, and no network
// identifiers. The registry base defaults to mainnet and the deposit admin is
// read from the wallet's own holdings.
const wallet = createPartyLayerWalletAdapter(partyLayerClient, {
party: 'yourparty::1220...', // the connected taker party
});
const handle = await executePlan(plan, {
wallet,
hooks: { onStatus: (s) => console.log(s.status, s.note) },
});
let state = await track(handle, { wallet });
while (!isPartnerTerminal(state.status)) {
await new Promise((r) => setTimeout(r, 3000));
state = await track(handle, { wallet });
}
console.log('final:', state.status, state.payoutAmount);
Not on PartyLayer? A hosted or self-signing wallet gets the same adapter
from one signer seam, no connect, no popup:
Hosted wallets. Beyond that, you can
implement the WalletAdapter interface yourself (five methods: sendDeposit, withdrawDeposit,
depositActive, observePayout, and the retired
lockFeeEscrow no-op) against any Canton wallet. The reference adapter
is the fast path; the interface is the escape hatch.
A terminal status is one of COMPLETED,
SLIPPAGE_FAILED, REFUNDED, or
ABORTED. Which wallets can drive this, and which venues
execute, is the support matrix.
The fee reality, up front
Do not ship a fee surprise. The current state, exactly:
- Fees are DISCLOSED, not COLLECTED on-ledger today. The server
flag
FEE_COLLECTION_ENABLEDisfalse, so every swap is fee-less right now. Your quote'sclientFeesand your plan'scollectsFees: falsesay so directly, the plan carries no fee step while the flag is off. - When the flag flips, the partner fee is planned and disclosed but not
executed by this SDK yet. The fee rides an atomic CIP-0112 batch with the deposit, a wallet-side capability.
The
WalletAdapterabove does not perform that batch, so the fee legs are not SDK-executed even after the flip, until@synfin/client's adapter gains batch capability. That is the current limitation, stated plainly. - The atomic path depends on the wallet's Canton participant carrying
splice-util-token-standard-wallet. Loop's participant currently does not, so a Loop-signed swap degrades to fee-less: the deposit stands, no fee is collected, and the outcome says so. Never a broken swap for a fee, never a silent charge.
When collection is on, your integrator fee is a separate leg and you keep
100 percent of it; Synfin's flat service fee is its own independent leg,
both disclosed in clientFees. How the two legs are built is in
Earn with your fee; the user-facing fee page is
How fees work.
Run the whole thing
The example repo is the same journey as a real, cloneable project:
git clone https://github.com/cayvox/synfin-examples
cd synfin-examples/quickstart-node
npm install
cp .env.example .env # add your SYNFIN_API_KEY
node --env-file=.env src/quote.mjs # a live quote, no funds move src/swap.mjs in that repo walks quote → plan → execute → track,
using the published @synfin/wallet-partylayer reference adapter.
Execution moves real funds on mainnet, so it runs only once you connect a real
wallet (see connect a wallet).
Try it before you sign up
You do not need a key to see a live quote: the quote endpoint answers keyless (venue nets, without your fee disclosure). One curl, no account:
curl "https://synfin.xyz/api/quote?from=CC&to=USDCx&amount=100"
The keyed path above adds your disclosed clientFees; a key also
unlocks planning and higher rate limits.