@temple-digital-group/temple-canton-js
v2.1.8
Published
JavaScript library for interacting with Temple Canton blockchain
Downloads
2,562
Readme
Temple Canton JS
JavaScript SDK for interacting with the Temple Canton blockchain exchange. Supports Amulet (CC), USDCx, CBTC, USDA, eXAU (Ember Gold), and eXAG (Ember Silver) tokens on the Canton network.
Installation
npm install @temple-digital-group/temple-canton-jsConfiguration
Call initialize() before using any SDK functions. It sets up the config and optionally authenticates with the Temple REST API.
Wallet Adapter
For apps using a supported wallet, just pass the wallet instance as WALLET_ADAPTER (or to setWalletAdapter). The SDK recognizes supported wallets and wraps them automatically with default settings — you don't need to call an adapter factory yourself.
- Loop: pass the Loop SDK instance directly. Works both in the browser and server-side (Node) — Loop signs locally on the server or delegates to the Loop Wallet UI in the browser.
import { initialize } from "@temple-digital-group/temple-canton-js";
// `wallet` is a supported wallet instance (e.g. a Loop SDK instance).
initialize({
API_KEY: "your-api-key",
NETWORK: "mainnet",
WALLET_ADAPTER: wallet,
});You can also set or change the wallet adapter after init:
import { setWalletAdapter } from "@temple-digital-group/temple-canton-js";
setWalletAdapter(wallet);Adapter Factories (advanced)
You only need the create*WalletAdapter() factories when you want to build the adapter explicitly instead of passing a raw instance (above), which already calls them for you with defaults. When you do, hand the result to WALLET_ADAPTER / setWalletAdapter:
createLoopWalletAdapter(loop)— normalize a Loop instance (no extra options).
import { setWalletAdapter, createLoopWalletAdapter } from "@temple-digital-group/temple-canton-js";
// Equivalent to passing the raw Loop instance — useful when you want the normalized adapter object.
setWalletAdapter(createLoopWalletAdapter(loop));| Key | Required | Description |
| ---------------- | ---------------- | ---------------------------------------------------------------------------------------------------- |
| API_KEY | Yes | Temple REST API key |
| NETWORK | Yes | mainnet or testnet |
| WALLET_ADAPTER | For ledger flows | Supported wallet instance or normalized adapter — required for deposits, withdrawals, and onboarding |
Network safety:
NETWORKis validated against an allowlist. Unknown, empty, or misspelled values throw instead of silently defaulting tomainnet, so a misconfiguration can never route orders or withdrawals to production.
Supported Instruments
| Asset | Type | Networks | | ----- | ----------- | ---------------- | | CC | Canton Coin | testnet, mainnet | | USDCx | Utility | testnet, mainnet | | CBTC | Utility | testnet, mainnet | | USDA | Utility | mainnet | | eXAU | Utility | testnet, mainnet | | eXAG | Utility | testnet, mainnet |
Symbol normalization: Use
CCfor Canton Coin in all SDK methods. The SDK handles the internal conversion toAmuletwhere required by the ledger. TheAmuletsymbol is deprecated — all API responses now returnCC.
Supported Trading Pairs
CC/USDCxCC/USDACBTC/USDCxCBTC/USDAUSDCx/USDAeXAU/USDCxeXAU/USDAeXAG/USDCxeXAG/USDA
v2 Trading Flow
The v2 flow covers the full trading lifecycle: onboarding, deposits, trading, and withdrawals.
1. Check onboarding → isUserOnboarded(party)
If NOT onboarded → onboardUser(party)
2. Deposit funds → deposit(amount, symbol)
3. Check balance → getTradingBalance()
4. Place orders → createOrderRequest({ symbol, side, quantity, price, ... })
5. Cancel orders → cancelOrder(orderId) or cancelAllOrders({ symbol })
6. Withdraw funds → withdrawFunds({ asset_id, amount })
7. Withdraw delegation → withdrawDelegation(delegationId, user)1. Onboarding
Check if a user has a delegation contract, and create one if not:
import { isUserOnboarded, onboardUser } from "@temple-digital-group/temple-canton-js";
const delegation = await isUserOnboarded(party);
if (!delegation) {
const result = await onboardUser({ partyId: party });
// result.delegation — the confirmed delegation contract
// result.warning — set if onboarding was submitted but not confirmed within 60s
}onboardUser submits the onboarding request and then polls isUserOnboarded every 5 seconds for up to 60 seconds. It returns once the delegation is confirmed, or with a warning field if the timeout is reached.
2. Deposit Funds
The simplest way to deposit is the deposit() helper — pass the amount and symbol, and it handles everything:
import { deposit } from "@temple-digital-group/temple-canton-js";
const result = await deposit(100, "USDCx");
// or
const result = await deposit(10, "CC");deposit() requires the wallet adapter to be connected. It:
- Checks your CC balance to ensure at least 10 CC is reserved for transaction fees
- For utility deposits (USDCx, CBTC, USDA, eXAU, eXAG), verifies you have enough of the token and 10 CC for fees
- Selects the right UTXOs from your wallet
- Submits the deposit allocation
If you need more control, use prepareDepositHoldings + depositFunds directly:
import { prepareDepositHoldings, depositFunds } from "@temple-digital-group/temple-canton-js";
const depositOpts = await prepareDepositHoldings(100, "USDCx");
const result = await depositFunds(depositOpts);Token Standard V2 deposits
On networks where the CLOB settles through Token Standard V2, deposit as a standing V2 allocation instead. It names no counterparty: it reserves the amount for the CLOB's settlement stream, every settlement spends from it and relocks the proceeds into it, so one deposit keeps working across trades. Several deposits simply add up.
import { depositV2, depositFundsV2 } from "@temple-digital-group/temple-canton-js";
const result = await depositV2(100, "USDCx"); // wallet adapter selects the holdings
// or with explicit holdings
const result = await depositFundsV2({ sender: party, assetId: "USDCx", amount: 100, holdingCids });RFQ Allocations
An RFQ trade hands each party an AllocationRequest (the trade leg) identified by its settlement reference, e.g. "<tradeId>:open". Fund the leg you send with one call; the settlement terms and the leg are copied verbatim from the request so the allocation can only ever settle that trade.
import { allocateForRfq, allocateForRfqV2, getAllocationRequestsForParty } from "@temple-digital-group/temple-canton-js";
// see what is waiting for you
const requests = await getAllocationRequestsForParty(party);
// Token Standard V1 leg
await allocateForRfq({ refId: `${tradeId}:open` });
// Token Standard V2 leg (also authorises the receiver side of the legs you receive under the same instrument admin)
await allocateForRfqV2({ refId: `${tradeId}:open` });
// when the request has you sending more than one instrument
await allocateForRfq({ refId, assetId: "USDCx" });Unlike CLOB deposits, an RFQ leg is funded exactly once: if you already hold an allocation for that reference and leg the call fails with you already have an allocation for ref ... instead of locking funds a second time. sender defaults to the wallet adapter's party and holdingCids are selected from the wallet when omitted; pass returnCommand = true as the second argument to get the ledger command instead of submitting it.
3. Trading Balance
import { getTradingBalance } from "@temple-digital-group/temple-canton-js";
const { balances, fee_balances } = await getTradingBalance();
// balances: [{ user_id, asset, unlocked, locked, in_flight, updated_at }] amounts are decimal STRINGS
// fee_balances: [{ asset, available, in_flight, locked, updated_at }] amounts are NUMBERSUse this to check available funds before placing orders or withdrawals.
The two arrays are not the same shape — do not reuse one model. fee_balances
is the prepaid fee balance; an empty array means the fee balance is not yet
available, not a confirmed zero.
4. Place Orders
import { createOrderRequest } from "@temple-digital-group/temple-canton-js";
const result = await createOrderRequest({
symbol: "CC/USDCx",
side: "buy",
quantity: 10.5,
price: 1.25,
order_type: "limit",
});
// Post-only order (rejected if it would match immediately)
const postOnly = await createOrderRequest({
symbol: "CC/USDCx",
side: "buy",
quantity: 10.5,
price: 1.25,
order_type: "limit",
order_subtype: "post_only",
});5. Cancel Orders
import { cancelOrder, cancelAllOrders } from "@temple-digital-group/temple-canton-js";
// Cancel a specific order
await cancelOrder("ord_abc123");
// Cancel all orders for a symbol
await cancelAllOrders({ symbol: "CC/USDCx" });
// Cancel ALL orders
await cancelAllOrders();6. Withdraw Funds
Withdraws available (unlocked, non-in-flight) trading balance back to the user's wallet.
import { withdrawFunds } from "@temple-digital-group/temple-canton-js";
const result = await withdrawFunds({
asset_id: "USDCx",
amount: "250.50",
});7. Withdraw Delegation
Archives the user's delegation contract. The user must re-onboard to trade again.
import { withdrawDelegation } from "@temple-digital-group/temple-canton-js";
// Auto-fetches delegation from API if not passed
await withdrawDelegation();
// Or pass explicitly
await withdrawDelegation(delegationContractId, partyId);Bulk Allocation Withdrawal
Withdraws one or more allocations of a single asset in one call by exercising Allocation_Withdraw on each allocation contract — use this to clear out all of a user's allocations (e.g. stuck or pending deposit allocations). All CIDs in a call must belong to the same assetId; to remove allocations across multiple assets, call it once per asset. The choice context and disclosed contracts are resolved automatically per allocation, exactly like a normal withdrawal.
import { buildAllocationWithdrawCommand } from "@temple-digital-group/temple-canton-js";
// Build AND submit withdraw commands for all of an asset's allocations
const result = await buildAllocationWithdrawCommand({
allocationCids: ["00abc123...", "00def456..."], // one CID or an array (same asset)
assetId: "USDCx",
submit: true,
});
// result.results — per-allocation { success, commandId, result | error }
// Or build only, and submit the commands yourself
const { commands } = await buildAllocationWithdrawCommand({
allocationCids: "00abc123...",
assetId: "CC",
});
// commands[i] — { command, endpoint } ready to submit| Option | Required | Description |
| ---------------- | -------- | ---------------------------------------------------------------------------------- |
| allocationCids | Yes | A single allocation contract ID or an array of them (all for the same asset) |
| assetId | Yes | Asset the allocations belong to (CC, USDCx, CBTC, USDA, eXAU, eXAG) |
| sender | No | Allocation owner party — defaults to the wallet adapter party |
| submit | No | When true, submits each command through the wallet adapter and returns results |
See docs/BUILD_ALLOCATION_WITHDRAW.md for the full parameter and return reference.
Get User Balances
import { getUserBalances } from "@temple-digital-group/temple-canton-js";
// Both params are optional — falls back to wallet adapter / config
const balances = await getUserBalances();
// Or pass explicitly
const balances = await getUserBalances(partyId);
const balances = await getUserBalances(partyId, walletProvider);Each entry in the returned array contains:
{
asset: 'USDCx',
total_balance: 170.5,
available_balance: 150.5,
locked_balance: 20.0,
dso: null,
registrar: '...',
operator: '...',
provider: '...',
merge_warning: true,
holdings: [...],
locked_holdings: [...],
utilityContext: { ... }
}Note: the level of detail depends on the data source. Wallets with a native balance summary (e.g. Loop's
getHolding) return totals only —holdings/locked_holdingsare empty andutilityContextisnull. Other wallet adapters return per-holding detail via active contracts (utilityContextstillnull, since resolving it needs ledger API access). Validator mode (VALIDATOR_API_URL) returns everything, includingutilityContext.
Merge Holdings
import { mergeAmuletHoldingsForParty, mergeUtilityHoldingsForParty, getAmuletDisclosures, getUtxoCount } from "@temple-digital-group/temple-canton-js";
// Merge all Amulet or utility holdings
await mergeAmuletHoldingsForParty(partyId);
await mergeUtilityHoldingsForParty(partyId, "USDCx");
// Wallet Provider — merge up to 5 smallest USDCx UTXOs
const command = await mergeUtilityHoldingsForParty(partyId, "USDCx", true, walletProvider, 5);
const result = await walletProvider.submitTransaction(command);
// Wallet Provider — merge CC (requires disclosures)
const disclosures = await getAmuletDisclosures(partyId);
const cmd = await mergeAmuletHoldingsForParty(partyId, true, walletProvider, 5, disclosures);
const res = await walletProvider.submitTransaction(cmd);
// Check UTXO status after merge
const counts = await getUtxoCount(partyId, "USDCx", walletProvider);WebSocket — Real-Time Data
Subscribe to live market data and user events via WebSocket. Works in both Node.js and browsers.
The server has two types of data:
- Market data — public channels you explicitly subscribe to (orderbook, trades, ticker, candles, oracle)
- User data — automatically pushed after authentication, no subscribe needed (orders, trades, balances)
import {
subscribeOrderbook,
subscribeTrades,
subscribeTicker,
subscribeCandles,
subscribeUserOrders,
subscribeUserTrades,
subscribeUserBalances,
disconnectWebSocket,
} from "@temple-digital-group/temple-canton-js";
// Market data — sends a subscribe message to the server
const unsub = subscribeOrderbook("CC/USDCx", (data) => {
console.log("Orderbook update:", data);
});
subscribeTrades("CC/USDCx", (data) => console.log("Trade:", data));
subscribeTicker("CBTC/USDCx", (data) => console.log("Ticker:", data));
subscribeCandles("CC/USDCx", 60, (data) => console.log("1m candle:", data));
// User data — auto-delivered after auth, no subscribe message needed
subscribeUserOrders((data) => console.log("Order update:", data));
subscribeUserTrades((data) => console.log("Trade fill:", data));
subscribeUserBalances((data) => console.log("Balance update:", data));
// Unsubscribe from a specific channel
unsub();
// Disconnect everything
disconnectWebSocket();Market Data Channels
These require an explicit subscribe message. The SDK handles this automatically.
| Function | Channel | Example |
| ------------------------------------------- | -------------------------------- | ------------------------- |
| subscribeOrderbook(symbol, cb) | orderbook:{symbol} | orderbook:Amulet/USDCx |
| subscribeTrades(symbol, cb) | trades:{symbol} | trades:Amulet/USDCx |
| subscribeTicker(symbol, cb) | ticker:{symbol} | ticker:CBTC/USDCx |
| subscribeCandles(symbol, granularity, cb) | candles:{symbol}:{granularity} | candles:Amulet/USDCx:60 |
| subscribeOracle(symbol, cb) | oracle:{symbol} | oracle:cc |
| subscribeOracleVolume(symbol, cb) | oracle_volume:{symbol} | oracle_volume:cc |
Pass
CCin symbols — the SDK normalizes it toAmuletin the wire channel name (e.g.subscribeOrderbook("CC/USDCx")subscribes toorderbook:Amulet/USDCx).
Candle granularity values: 60 (1m), 300 (5m), 900 (15m), 3600 (1h), 14400 (4h), 86400 (1d)
User Data Events
Pushed automatically by the server after authentication. No subscribe message is sent — you just register a local handler. Requires API_KEY (Node.js) or cookie auth (browser).
| Function | Server Event | Description |
| --------------------------- | -------------- | ---------------------------------------------------- |
| subscribeUserOrders(cb) | user_order | Order lifecycle updates (created, filled, cancelled) |
| subscribeUserTrades(cb) | user_trade | Trade fill confirmations |
| subscribeUserBalances(cb) | user_balance | Balance changes |
Advanced Usage
Use the TempleWebSocket class directly for full control:
import { TempleWebSocket } from "@temple-digital-group/temple-canton-js";
const ws = new TempleWebSocket();
ws.onConnect = () => console.log("Connected");
ws.onDisconnect = (code, reason) => console.log("Disconnected:", code, reason);
ws.onAuth = (success, userId) => console.log("Auth:", success, userId);
ws.onError = (err) => console.error("WS error:", err);
ws.autoReconnect = true; // default — reconnects with exponential backoff
ws.connect();
// Market data — sends subscribe to server
const unsub = ws.subscribe("orderbook:Amulet/USDCx", (data) => { ... });
// User data — no subscribe message, just local handler
const unsubOrder = ws.onUserEvent("user_order", (data) => { ... });API Reference
Functions marked with W support configured wallets via the wallet adapter.
Configuration
| Function | Description |
| --------------------------- | ----------------------------------------------------------------------------- |
| initialize(config) | Initialize the SDK, set config, and optionally authenticate with the REST API |
| setWalletAdapter(adapter) | Set or update the wallet adapter for all wallet-aware functions |
Instrument Catalog
| Function | Description |
| ---------------------------- | --------------------------------------- |
| getSupportedTradingPairs() | Get the list of supported trading pairs |
| getInstrumentCatalog() | Get the full instrument catalog |
Onboarding & Delegation
| Function | Provider | Description |
| ------------------------------------------ | -------- | ------------------------------------------------------ |
| isUserOnboarded(party) | W | Check if user has a delegation contract on ledger |
| onboardUser({ partyId }) | W | Create the delegation contract needed for trading |
| withdrawDelegation(delegationId?, user?) | W | Archive the delegation contract (user must re-onboard) |
Deposits & Withdrawals
| Function | Provider | Description |
| ----------------------------------------- | -------- | ----------------------------------------------------------------- |
| deposit(amount, symbol) | W | Deposit funds (validates balance, reserves 10 CC for fees) |
| prepareDepositHoldings(amount, assetId) | W | Resolve holdings for a deposit amount (low-level) |
| depositFunds(opts) | W | Submit deposit allocation (low-level) |
| depositV2(amount, symbol) | W | Deposit as a Token Standard V2 standing allocation |
| depositFundsV2(opts) | W | Submit a V2 standing allocation with explicit holdings (low-level) |
| allocateForRfq(opts) | W | Fund an RFQ leg (refId) with a V1 allocation; rejects duplicates |
| allocateForRfqV2(opts) | W | Fund an RFQ leg (refId) with a V2 allocation; rejects duplicates |
| getAllocationRequestsForParty(party) | L | Open AllocationRequest contracts (RFQ legs) with their legs |
| findAllocationRequestByRef(party, refId)| L | One AllocationRequest by its settlement reference |
| withdrawFunds({ asset_id, amount }) | W | Withdraw available trading balance back to wallet |
| depositFees(amount, opts?) | W | Top up the prepaid fee balance (transfer to the fee party) |
| buildAllocationWithdrawCommand(opts) | W | Bulk-withdraw one or more allocations (optionally auto-submit) |
| finalizeWithdrawFunds(opts) | W | Exercise Allocation_Withdraw on a single allocation (low-level) |
Holdings
| Function | Provider | Description |
| ----------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------- |
| getUserBalances(party?, provider?) | W | Get all balances grouped by asset (Amulet, locked, and utility) |
| getAmuletHoldingsForParty(party, returnCommand, provider) | W | Get Amulet holdings |
| getLockedAmuletHoldingsForParty(party, returnCommand, provider) | W | Get locked Amulet holdings |
| getUtilityHoldingsForParty(party, returnCommand, provider) | W | Get utility token holdings |
| getUtxoCount(party, assetId, provider) | W | Get UTXO summary: counts, largest unlocked amount, and total unlocked balance |
Holding Operations
| Function | Provider | Description |
| ------------------------------------------------------------------------------------------ | -------- | ------------------------------- |
| mergeAmuletHoldingsForParty(party, returnCommand, provider, maxUtxos, amuletDisclosures) | W | Merge Amulet holdings into one |
| mergeUtilityHoldingsForParty(party, utilityAsset, returnCommand, provider, maxUtxos) | W | Merge utility holdings into one |
Temple REST API
These functions call the Temple REST API. Pass
API_KEYininitialize()to authenticate.
Auth
| Function | Description |
| ------------- | ---------------------- |
| getUserId() | Get the stored user ID |
Market Data
| Function | Description |
| ----------------------------------- | --------------------------------------------------------- |
| getTicker(symbol?) | Get ticker data for one or all trading pairs |
| getOrderBook(symbol, options?) | Get the order book (options: levels, precision) |
| getSymbolConfig(symbol) | Get symbol configuration (paused, decimals, min quantity) |
| getOpenInterest(symbol) | Get open interest for a trading pair |
| getRecentTrades(symbol, options?) | Get recent trades (options: limit, max 500) |
Trading
| Function | Description |
| --------------------------- | ---------------------------------------------- |
| createOrderRequest(opts) | Place a buy/sell order via the trading backend |
| cancelOrder(orderId) | Cancel a specific order |
| cancelAllOrders(options?) | Cancel all orders (options: symbol filter) |
| getTradingBalance() | Get trading balances and prepaid fee balances |
| getActiveOrders(options?) | Get active orders (options: symbol, limit) |
Fees
| Function | Description |
| ---------------------------- | -------------------------------------------------------------------- |
| getFeeConfig() | Fee enabled flag, fee party (deposit address), and fee asset details |
| getFeeDeposits(options?) | Paginated fee top-up history (options: limit, cursor) |
| getFeeDeductions(options?) | Paginated per-trade fees charged (options: limit, cursor) |
limit defaults to 50 and is capped at 100. cursor is opaque — pass
next_cursor back verbatim and loop until has_more is false. Never decode
or synthesize a cursor; an invalid one is a hard 400.
import { getFeeDeductions } from "@temple-digital-group/temple-canton-js";
let cursor;
do {
const page = await getFeeDeductions({ limit: 100, cursor });
if (page.error) break;
console.log(page.deductions);
cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);Top up the prepaid fee balance — reads /api/fees for the fee party and fee
asset, then hands the transfer to the wallet, which selects the holdings and signs:
import { depositFees } from "@temple-digital-group/temple-canton-js";
const result = await depositFees(250);Requires a wallet adapter whose wallet supports transfers. walletTransfer() is
exported for arbitrary transfers:
import { walletTransfer } from "@temple-digital-group/temple-canton-js";
await walletTransfer({
recipient: "party::1220abc",
amount: 100,
instrument: { instrument_id: "USDA", instrument_admin: "usda-admin::1220abc" },
message: "optional memo",
});During temporary service unavailability these endpoints return HTTP 503; the returned error
carries retryable: true and retryAfter (seconds).
Withdrawals
| Function | Description |
| ------------------------------------------ | ------------------------------------------ |
| createWithdrawalRequest(assetId, amount) | Submit a withdrawal request to the backend |
| getWithdrawalRequestStatus(requestId) | Poll withdrawal status until ready |
Disclosures & Delegation
| Function | Description |
| ------------------------- | ---------------------------------------------------------------------------- |
| getDisclosures(partyId) | Get Amulet disclosure data (factory ID, choice context, disclosed contracts) |
| getDelegation() | Get the user's delegation contract from the API |
WebSocket
| Function | Description |
| ------------------------------------------- | -------------------------------------------------------------- |
| createWebSocket() | Get or create the shared WS instance (auto-connects) |
| disconnectWebSocket() | Disconnect and destroy the shared WS instance |
| subscribeOrderbook(symbol, cb) | Subscribe to orderbook updates |
| subscribeTrades(symbol, cb) | Subscribe to trade updates |
| subscribeTicker(symbol, cb) | Subscribe to ticker updates |
| subscribeCandles(symbol, granularity, cb) | Subscribe to candle updates |
| subscribeOracle(symbol, cb) | Subscribe to oracle price updates |
| subscribeOracleVolume(symbol, cb) | Subscribe to oracle volume updates |
| subscribeUserOrders(cb) | Listen to user order events (auto-pushed, no subscribe needed) |
| subscribeUserTrades(cb) | Listen to user trade events (auto-pushed, no subscribe needed) |
