@temple-digital-group/temple-canton-js 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -109,7 +109,6 @@ const result = await deposit(10, "CC");
109
109
  ```
110
110
 
111
111
  `deposit()` requires the wallet adapter to be connected. It:
112
-
113
112
  1. Checks your CC balance to ensure at least 10 CC is reserved for transaction fees
114
113
  2. For utility deposits (USDCx, CBTC), verifies you have enough of the token **and** 10 CC for fees
115
114
  3. Selects the right UTXOs from your wallet
@@ -302,10 +301,10 @@ These require an explicit subscribe message. The SDK handles this automatically.
302
301
 
303
302
  | Function | Channel | Example |
304
303
  | ------------------------------------------- | -------------------------------- | ------------------------- |
305
- | `subscribeOrderbook(symbol, cb)` | `orderbook:{symbol}` | `orderbook:Amulet/USDCx` |
306
- | `subscribeTrades(symbol, cb)` | `trades:{symbol}` | `trades:Amulet/USDCx` |
307
- | `subscribeTicker(symbol, cb)` | `ticker:{symbol}` | `ticker:CBTC/USDCx` |
308
- | `subscribeCandles(symbol, granularity, cb)` | `candles:{symbol}:{granularity}` | `candles:Amulet/USDCx:60` |
304
+ | `subscribeOrderbook(symbol, cb)` | `orderbook:{symbol}` | `orderbook:Amulet/USDCx` |
305
+ | `subscribeTrades(symbol, cb)` | `trades:{symbol}` | `trades:Amulet/USDCx` |
306
+ | `subscribeTicker(symbol, cb)` | `ticker:{symbol}` | `ticker:CBTC/USDCx` |
307
+ | `subscribeCandles(symbol, granularity, cb)` | `candles:{symbol}:{granularity}` | `candles:Amulet/USDCx:60`|
309
308
  | `subscribeOracle(symbol, cb)` | `oracle:{symbol}` | `oracle:cc` |
310
309
  | `subscribeOracleVolume(symbol, cb)` | `oracle_volume:{symbol}` | `oracle_volume:cc` |
311
310
 
@@ -315,11 +314,11 @@ These require an explicit subscribe message. The SDK handles this automatically.
315
314
 
316
315
  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).
317
316
 
318
- | Function | Server Event | Description |
319
- | --------------------------- | -------------- | ---------------------------------------------------- |
320
- | `subscribeUserOrders(cb)` | `user_order` | Order lifecycle updates (created, filled, cancelled) |
321
- | `subscribeUserTrades(cb)` | `user_trade` | Trade fill confirmations |
322
- | `subscribeUserBalances(cb)` | `user_balance` | Balance changes |
317
+ | Function | Server Event | Description |
318
+ | -------------------------- | -------------- | -------------------------------------------------- |
319
+ | `subscribeUserOrders(cb)` | `user_order` | Order lifecycle updates (created, filled, cancelled)|
320
+ | `subscribeUserTrades(cb)` | `user_trade` | Trade fill confirmations |
321
+ | `subscribeUserBalances(cb)`| `user_balance` | Balance changes |
323
322
 
324
323
  ### Advanced Usage
325
324
 
@@ -371,13 +370,13 @@ const unsubOrder = ws.onUserEvent("user_order", (data) => { ... });
371
370
 
372
371
  ### Deposits & Withdrawals
373
372
 
374
- | Function | Provider | Description |
375
- | ----------------------------------------- | -------- | ---------------------------------------------------------- |
373
+ | Function | Provider | Description |
374
+ | ----------------------------------------- | -------- | ----------------------------------------------------- |
376
375
  | `deposit(amount, symbol)` | **W** | Deposit funds (validates balance, reserves 10 CC for fees) |
377
- | `prepareDepositHoldings(amount, assetId)` | **W** | Resolve holdings for a deposit amount (low-level) |
378
- | `depositFunds(opts)` | **W** | Submit deposit allocation (low-level) |
379
- | `withdrawFunds({ asset_id, amount })` | **W** | Withdraw available trading balance back to wallet |
380
- | `bulkWithdrawFunds(opts)` | **W** | Cancel all orders and withdraw everything immediately |
376
+ | `prepareDepositHoldings(amount, assetId)` | **W** | Resolve holdings for a deposit amount (low-level) |
377
+ | `depositFunds(opts)` | **W** | Submit deposit allocation (low-level) |
378
+ | `withdrawFunds({ asset_id, amount })` | **W** | Withdraw available trading balance back to wallet |
379
+ | `emergencyWithdrawFunds(opts)` | **W** | Cancel all orders and withdraw everything immediately |
381
380
 
382
381
  ### Holdings
383
382
 
@@ -419,13 +418,13 @@ const unsubOrder = ws.onUserEvent("user_order", (data) => { ... });
419
418
 
420
419
  #### Trading
421
420
 
422
- | Function | Description |
423
- | --------------------------- | ------------------------------------------------------ |
424
- | `createOrderRequest(opts)` | Place a buy/sell order via the trading backend |
425
- | `cancelOrder(orderId)` | Cancel a specific order |
426
- | `cancelAllOrders(options?)` | Cancel all orders (options: `symbol` filter) |
427
- | `getTradingBalance()` | Get user's trading balance (unlocked/locked/in-flight) |
428
- | `getActiveOrders(options?)` | Get active orders (options: `symbol`, `limit`) |
421
+ | Function | Description |
422
+ | ----------------------------------- | ------------------------------------------------------ |
423
+ | `createOrderRequest(opts)` | Place a buy/sell order via the trading backend |
424
+ | `cancelOrder(orderId)` | Cancel a specific order |
425
+ | `cancelAllOrders(options?)` | Cancel all orders (options: `symbol` filter) |
426
+ | `getTradingBalance()` | Get user's trading balance (unlocked/locked/in-flight) |
427
+ | `getActiveOrders(options?)` | Get active orders (options: `symbol`, `limit`) |
429
428
 
430
429
  #### Withdrawals
431
430
 
@@ -436,20 +435,23 @@ const unsubOrder = ws.onUserEvent("user_order", (data) => { ... });
436
435
 
437
436
  #### Disclosures & Delegation
438
437
 
439
- | Function | Description |
440
- | ------------------ | ---------------------------------------------------------------------------- |
441
- | `getDisclosures()` | Get Amulet disclosure data (factory ID, choice context, disclosed contracts) |
442
- | `getDelegation()` | Get the user's delegation contract from the API |
438
+ | Function | Description |
439
+ | ------------------------- | ---------------------------------------------------------------------------- |
440
+ | `getDisclosures(partyId)` | Get Amulet disclosure data (factory ID, choice context, disclosed contracts) |
441
+ | `getDelegation()` | Get the user's delegation contract from the API |
443
442
 
444
443
  ### WebSocket
445
444
 
446
- | Function | Description |
447
- | ------------------------------------------- | ---------------------------------------------------- |
448
- | `createWebSocket()` | Get or create the shared WS instance (auto-connects) |
449
- | `disconnectWebSocket()` | Disconnect and destroy the shared WS instance |
450
- | `subscribeOrderbook(symbol, cb)` | Subscribe to orderbook updates |
451
- | `subscribeTrades(symbol, cb)` | Subscribe to trade updates |
452
- | `subscribeTicker(symbol, cb)` | Subscribe to ticker updates |
453
- | `subscribeCandles(symbol, granularity, cb)` | Subscribe to candle updates |
454
- | `subscribeOracle(symbol, cb)` | Subscribe to oracle price updates |
455
- | `subscribeOracleVolume(symbol, cb)` | Subscribe to oracle volume updates |
445
+ | Function | Description |
446
+ | ------------------------------------------- | -------------------------------------------------------------- |
447
+ | `createWebSocket()` | Get or create the shared WS instance (auto-connects) |
448
+ | `disconnectWebSocket()` | Disconnect and destroy the shared WS instance |
449
+ | `subscribeOrderbook(symbol, cb)` | Subscribe to orderbook updates |
450
+ | `subscribeTrades(symbol, cb)` | Subscribe to trade updates |
451
+ | `subscribeTicker(symbol, cb)` | Subscribe to ticker updates |
452
+ | `subscribeCandles(symbol, granularity, cb)` | Subscribe to candle updates |
453
+ | `subscribeOracle(symbol, cb)` | Subscribe to oracle price updates |
454
+ | `subscribeOracleVolume(symbol, cb)` | Subscribe to oracle volume updates |
455
+ | `subscribeUserOrders(cb)` | Listen to user order events (auto-pushed, no subscribe needed) |
456
+ | `subscribeUserTrades(cb)` | Listen to user trade events (auto-pushed, no subscribe needed) |
457
+ | `subscribeUserBalances(cb)` | Listen to user balance events (auto-pushed, no subscribe needed)|
@@ -3,7 +3,7 @@ import axios from "axios";
3
3
  import { getDisclosures } from "../api/index.js";
4
4
  import { getUserId } from "../api/tokenStore.js";
5
5
  import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "../../src/canton/walletAdapter.js";
6
- import { normalizeAssetId, instrumentCatalog } from "../../src/canton/instrumentCatalog.js";
6
+ import { normalizeAssetId, instrumentCatalog, instrumentIdToSymbol, resolveOnChainInstrumentId } from "../../src/canton/instrumentCatalog.js";
7
7
  import { randomUUID, shouldUseLedgerForMetadata, normalizeContractId, resolveInstrumentDefinition, getInstrumentRegistrar, resolveProvider, dedupeDisclosedContracts, buildHeaders, DEFAULT_UTILITY_CONTEXT_KEYS, } from "./helpers.js";
8
8
  import { resolveAmuletContext, resolveUtilityInstrumentConfiguration, resolveUtilityAllocationFactory, getAmuletHoldingsForParty, getUtilityHoldingsForParty, getUtxoCount, } from "../../src/canton/index.js";
9
9
  // ─── Constants ───────────────────────────────────────────────────────────────
@@ -48,14 +48,16 @@ export async function prepareDepositHoldings(amount, symbol) {
48
48
  if (!createdEvent)
49
49
  continue;
50
50
  const createArg = createdEvent.createArgument;
51
- // Skip asset types that don't match the requested symbol
52
- const instrument = createArg?.instrument;
53
- const createdAssetId = instrument?.id || (isAmulet ? "Amulet" : null);
54
- if (createdAssetId !== assetId)
55
- continue;
56
- // Skip locked utility holdings
57
- if (!isAmulet && createArg?.lock)
58
- continue;
51
+ // Utility holdings: filter by asset type and drop locked ones.
52
+ // Amulet holdings come pre-filtered by template, so no further checks needed.
53
+ if (!isAmulet) {
54
+ if (createArg?.lock)
55
+ continue;
56
+ const onChainId = createArg?.instrument?.id;
57
+ const heldSymbol = instrumentIdToSymbol[onChainId] || onChainId;
58
+ if (heldSymbol !== assetId)
59
+ continue;
60
+ }
59
61
  const amountField = createArg?.amount;
60
62
  const quantity = isAmulet
61
63
  ? parseFloat(amountField?.initialAmount || "0")
@@ -237,7 +239,7 @@ export async function depositFunds(opts, returnCommand = false) {
237
239
  amount: parseFloat(parseFloat(String(amount)).toFixed(10)).toString(),
238
240
  instrumentId: {
239
241
  admin: admin,
240
- id: assetId,
242
+ id: resolveOnChainInstrumentId(assetId),
241
243
  },
242
244
  meta: { values: {} },
243
245
  },
@@ -17,13 +17,6 @@ export interface FinalizeWithdrawOpts {
17
17
  };
18
18
  userId?: string;
19
19
  }
20
- export interface BulkWithdrawFundsOpts {
21
- allocationIds: string[];
22
- sender?: string;
23
- assetId: string;
24
- disclosures?: FinalizeWithdrawOpts["disclosures"];
25
- userId?: string;
26
- }
27
20
  export interface WithdrawFundsOpts {
28
21
  asset_id: string;
29
22
  amount: string | number;
@@ -39,19 +32,6 @@ export interface WithdrawFundsOpts {
39
32
  * 3. Remote: Scan API / Registry API
40
33
  */
41
34
  export declare function finalizeWithdrawFunds(opts: FinalizeWithdrawOpts, returnCommand?: boolean): Promise<Record<string, unknown>>;
42
- /**
43
- * Finalize multiple withdrawals in a single signed transaction.
44
- *
45
- * Bundles N Allocation_Withdraw exercises into one ledger submission so the user
46
- * signs once for the whole batch. All allocations must belong to the same sender
47
- * and the same asset (caller's responsibility — pass one assetId).
48
- *
49
- * Choice-context fetching:
50
- * - Localhost ledger / Amulet via /api/amulet/disclosures: one shared context for all N.
51
- * - Scan API (Amulet) / Registry API (utility): one fetch per allocation, then disclosed
52
- * contracts merged and deduped.
53
- */
54
- export declare function bulkWithdrawFunds(opts: BulkWithdrawFundsOpts, returnCommand?: boolean): Promise<Record<string, unknown>>;
55
35
  /**
56
36
  * High-level withdrawal flow:
57
37
  * 1. Creates a withdrawal request via the backend API.
@@ -258,271 +258,6 @@ export async function finalizeWithdrawFunds(opts, returnCommand = false) {
258
258
  return { error: msg };
259
259
  }
260
260
  }
261
- /**
262
- * Finalize multiple withdrawals in a single signed transaction.
263
- *
264
- * Bundles N Allocation_Withdraw exercises into one ledger submission so the user
265
- * signs once for the whole batch. All allocations must belong to the same sender
266
- * and the same asset (caller's responsibility — pass one assetId).
267
- *
268
- * Choice-context fetching:
269
- * - Localhost ledger / Amulet via /api/amulet/disclosures: one shared context for all N.
270
- * - Scan API (Amulet) / Registry API (utility): one fetch per allocation, then disclosed
271
- * contracts merged and deduped.
272
- */
273
- export async function bulkWithdrawFunds(opts, returnCommand = false) {
274
- const { allocationIds, sender: senderOpt, assetId: rawAssetId, disclosures, userId } = opts;
275
- const assetId = normalizeAssetId(rawAssetId);
276
- const sender = getAdapterPartyId() ?? senderOpt ?? config.VALIDATOR_USER_PARTY_ID;
277
- if (!Array.isArray(allocationIds) || allocationIds.length === 0) {
278
- const msg = "bulkWithdrawFunds: allocationIds must be a non-empty array";
279
- console.error(msg);
280
- return { error: msg };
281
- }
282
- if (!sender || !assetId) {
283
- const msg = "bulkWithdrawFunds: sender and assetId are required";
284
- console.error(msg);
285
- return { error: msg };
286
- }
287
- const isAmulet = assetId === "Amulet";
288
- const allocationCids = allocationIds.map((cid) => normalizeContractId(cid));
289
- // Per-allocation choice context (one entry per allocation, in the same order).
290
- // Shared-context paths fill all N entries with the same object.
291
- let perAllocContexts = [];
292
- let mergedDisclosedContracts = [];
293
- if (shouldUseLedgerForMetadata()) {
294
- // Localhost: resolve once, reuse for all allocations.
295
- let sharedContext = {};
296
- const sharedDisclosed = [];
297
- if (isAmulet) {
298
- const amuletCtx = await resolveAmuletContext({ investor: sender, holdingIds: [], transferAmount: 0 });
299
- if (!amuletCtx) {
300
- const msg = "bulkWithdrawFunds: failed to resolve Amulet context from ledger";
301
- console.error(msg);
302
- return { error: msg };
303
- }
304
- const ctx = amuletCtx;
305
- const contextKeys = ctx.contextKeys;
306
- const amuletRules = ctx.amuletRules;
307
- const openMiningRound = ctx.openMiningRound;
308
- const featuredAppRight = ctx.featuredAppRight;
309
- const externalAmuletRules = ctx.externalAmuletRules;
310
- sharedContext = {
311
- values: {
312
- [contextKeys.amuletRules]: { tag: "AV_ContractId", value: amuletRules.contractCid },
313
- [contextKeys.openRound]: { tag: "AV_ContractId", value: openMiningRound.contractCid },
314
- [contextKeys.featuredAppRight]: { tag: "AV_ContractId", value: featuredAppRight.contractCid },
315
- [contextKeys.expireLock]: { tag: "AV_Bool", value: true },
316
- },
317
- };
318
- sharedDisclosed.push({
319
- templateId: amuletRules.templateId ?? null,
320
- contractId: amuletRules.contractCid,
321
- createdEventBlob: amuletRules.disclosureCid,
322
- synchronizerId: amuletRules.synchronizerId,
323
- }, {
324
- templateId: openMiningRound.templateId ?? null,
325
- contractId: openMiningRound.contractCid,
326
- createdEventBlob: openMiningRound.disclosureCid,
327
- synchronizerId: openMiningRound.synchronizerId,
328
- }, {
329
- templateId: externalAmuletRules.templateId ?? null,
330
- contractId: externalAmuletRules.contractCid,
331
- createdEventBlob: externalAmuletRules.disclosureCid,
332
- synchronizerId: externalAmuletRules.synchronizerId,
333
- });
334
- if (featuredAppRight?.contractCid && featuredAppRight?.disclosureCid) {
335
- sharedDisclosed.push({
336
- templateId: featuredAppRight.templateId ?? null,
337
- contractId: featuredAppRight.contractCid,
338
- createdEventBlob: featuredAppRight.disclosureCid,
339
- synchronizerId: featuredAppRight.synchronizerId,
340
- });
341
- }
342
- }
343
- else {
344
- const instrumentDef = resolveInstrumentDefinition(assetId);
345
- const networkDef = instrumentDef?.[config.NETWORK];
346
- const registrar = networkDef?.registrar || getInstrumentRegistrar(assetId) || config.VALIDATOR_REGISTRAR_PARTY_ID;
347
- const [allocFactory, instConfig] = await Promise.all([
348
- resolveUtilityAllocationFactory(registrar, sender),
349
- resolveUtilityInstrumentConfiguration(assetId, registrar),
350
- ]);
351
- if (instConfig) {
352
- sharedContext = {
353
- values: {
354
- [DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfiguration]: {
355
- tag: "AV_ContractId",
356
- value: instConfig.contractCid,
357
- },
358
- [DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfigurationPrefixed]: {
359
- tag: "AV_ContractId",
360
- value: instConfig.contractCid,
361
- },
362
- [DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentials]: { tag: "AV_List", value: [] },
363
- [DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentialsPrefixed]: { tag: "AV_List", value: [] },
364
- [DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentials]: { tag: "AV_List", value: [] },
365
- [DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentialsPrefixed]: { tag: "AV_List", value: [] },
366
- [DEFAULT_AMULET_CONTEXT_KEYS.expireLock]: { tag: "AV_Bool", value: true },
367
- },
368
- };
369
- sharedDisclosed.push({
370
- templateId: null,
371
- contractId: instConfig.contractCid,
372
- createdEventBlob: instConfig.disclosureCid,
373
- synchronizerId: instConfig.synchronizerId,
374
- });
375
- }
376
- if (allocFactory) {
377
- sharedDisclosed.push({
378
- templateId: null,
379
- contractId: allocFactory.contractCid,
380
- createdEventBlob: allocFactory.disclosureCid,
381
- synchronizerId: allocFactory.synchronizerId,
382
- });
383
- }
384
- }
385
- perAllocContexts = allocationCids.map(() => sharedContext);
386
- mergedDisclosedContracts = sharedDisclosed;
387
- }
388
- else if (isAmulet && !config.VALIDATOR_SCAN_API_URL) {
389
- // Amulet via /api/amulet/disclosures: one fetch shared across all allocations.
390
- const factoryData = disclosures?.disclosures || disclosures || null;
391
- let resolvedData = factoryData;
392
- if (!resolvedData?.choiceContext) {
393
- const disclosuresResult = (await getDisclosures(sender));
394
- resolvedData = disclosuresResult?.disclosures;
395
- if (disclosuresResult?.error || !resolvedData?.choiceContext) {
396
- const detail = disclosuresResult?.message ||
397
- disclosuresResult?.error ||
398
- "missing choiceContext in response";
399
- const msg = `bulkWithdrawFunds: failed to resolve Amulet disclosures: ${detail}`;
400
- console.error(msg);
401
- return { error: msg };
402
- }
403
- }
404
- const choiceContext = resolvedData.choiceContext;
405
- const sharedContext = choiceContext.choiceContextData || {};
406
- const values = sharedContext.values;
407
- if (values && !values[DEFAULT_AMULET_CONTEXT_KEYS.expireLock]) {
408
- values[DEFAULT_AMULET_CONTEXT_KEYS.expireLock] = { tag: "AV_Bool", value: true };
409
- }
410
- const sharedDisclosed = (choiceContext.disclosedContracts || []).map((dc) => ({
411
- templateId: dc.templateId,
412
- contractId: dc.contractId,
413
- createdEventBlob: dc.createdEventBlob,
414
- synchronizerId: dc.synchronizerId,
415
- }));
416
- perAllocContexts = allocationCids.map(() => sharedContext);
417
- mergedDisclosedContracts = sharedDisclosed;
418
- }
419
- else {
420
- // Per-allocation context: Scan API (Amulet) or Registry API (utility).
421
- let baseUrl;
422
- let contextHeaders = {};
423
- if (isAmulet) {
424
- if (!config.VALIDATOR_SCAN_API_URL) {
425
- const msg = "bulkWithdrawFunds: VALIDATOR_SCAN_API_URL is required for Amulet allocations";
426
- console.error(msg);
427
- return { error: msg };
428
- }
429
- baseUrl = `${config.VALIDATOR_SCAN_API_URL}/registry`;
430
- contextHeaders = await buildHeaders();
431
- }
432
- else {
433
- const instrumentDef = resolveInstrumentDefinition(assetId);
434
- const networkContracts = instrumentDef?.[config.NETWORK];
435
- const registryAPI = networkContracts?.registryAPI;
436
- if (!registryAPI) {
437
- const msg = `bulkWithdrawFunds: no registryAPI defined for ${assetId} on ${config.NETWORK}`;
438
- console.error(msg);
439
- return { error: msg };
440
- }
441
- baseUrl = registryAPI;
442
- }
443
- const requestConfig = Object.keys(contextHeaders).length > 0 ? { headers: contextHeaders } : undefined;
444
- try {
445
- const contextResults = await Promise.all(allocationCids.map(async (cid) => {
446
- const url = `${baseUrl}/allocations/v1/${encodeURIComponent(cid)}/choice-contexts/withdraw`;
447
- try {
448
- const resp = await axios.post(url, { meta: {}, excludeDebugFields: true }, requestConfig);
449
- return resp.data;
450
- }
451
- catch (error) {
452
- const axiosErr = error;
453
- const detail = axiosErr.response?.data ? JSON.stringify(axiosErr.response.data) : axiosErr.message;
454
- throw new Error(`bulkWithdrawFunds: error fetching withdraw context for ${cid} from ${isAmulet ? "Scan API" : "Registry API"}: ${detail}`);
455
- }
456
- }));
457
- for (const data of contextResults) {
458
- perAllocContexts.push(data?.choiceContextData || {});
459
- const disclosed = (data?.disclosedContracts || []).map((dc) => ({
460
- templateId: dc.templateId,
461
- contractId: dc.contractId,
462
- createdEventBlob: dc.createdEventBlob,
463
- synchronizerId: dc.synchronizerId,
464
- }));
465
- mergedDisclosedContracts.push(...disclosed);
466
- }
467
- }
468
- catch (error) {
469
- const msg = error.message;
470
- console.error(msg);
471
- return { error: msg };
472
- }
473
- }
474
- // Build one ExerciseCommand per allocation; Canton signs the whole submission atomically.
475
- const exerciseCommands = allocationCids.map((cid, idx) => ({
476
- ExerciseCommand: {
477
- templateId: "#splice-api-token-allocation-v1:Splice.Api.Token.AllocationV1:Allocation",
478
- contractId: cid,
479
- choice: "Allocation_Withdraw",
480
- choiceArgument: {
481
- extraArgs: {
482
- context: perAllocContexts[idx],
483
- meta: { values: {} },
484
- },
485
- },
486
- },
487
- }));
488
- const command = {
489
- commands: exerciseCommands,
490
- commandId: randomUUID(),
491
- userId: userId || getUserId() || config.AUTH0_USER_ID || "temple",
492
- applicationId: "temple",
493
- actAs: [sender],
494
- disclosedContracts: mergedDisclosedContracts,
495
- };
496
- dedupeDisclosedContracts(command);
497
- const endpoint = `${config.VALIDATOR_API_URL}/v2/commands/submit-and-wait`;
498
- if (returnCommand) {
499
- return { command, endpoint };
500
- }
501
- if (getWalletAdapter()) {
502
- try {
503
- await payDueGasIfAny();
504
- return (await submitCommand(command));
505
- }
506
- catch (error) {
507
- const msg = `bulkWithdrawFunds: wallet adapter submission failed: ${error.message}`;
508
- console.error(msg);
509
- return { error: msg };
510
- }
511
- }
512
- const headers = await buildHeaders();
513
- try {
514
- const response = await axios.post(endpoint, command, { headers });
515
- return response.data;
516
- }
517
- catch (error) {
518
- const axiosErr = error;
519
- const errorData = axiosErr?.response?.data;
520
- const errorDetail = errorData ? JSON.stringify(errorData, null, 2) : axiosErr.message;
521
- const msg = `bulkWithdrawFunds: error submitting command: ${errorDetail}`;
522
- console.error(msg);
523
- return { error: msg };
524
- }
525
- }
526
261
  /**
527
262
  * High-level withdrawal flow:
528
263
  * 1. Creates a withdrawal request via the backend API.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temple-digital-group/temple-canton-js",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "JavaScript library for interacting with Temple Canton blockchain",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -3,7 +3,7 @@ import axios from "axios";
3
3
  import { getDisclosures } from "../api/index.js";
4
4
  import { getUserId } from "../api/tokenStore.js";
5
5
  import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "../../src/canton/walletAdapter.js";
6
- import { normalizeAssetId, instrumentCatalog } from "../../src/canton/instrumentCatalog.js";
6
+ import { normalizeAssetId, instrumentCatalog, instrumentIdToSymbol, resolveOnChainInstrumentId } from "../../src/canton/instrumentCatalog.js";
7
7
  import {
8
8
  randomUUID,
9
9
  shouldUseLedgerForMetadata,
@@ -94,7 +94,6 @@ export async function prepareDepositHoldings(
94
94
  if (!holdings || (holdings as unknown[]).length === 0) {
95
95
  return { error: `prepareDepositHoldings: no holdings found for party ${party}` };
96
96
  }
97
-
98
97
  // Parse holdings into { contractId, quantity } and filter out locked
99
98
  const parsed: { contractId: string; quantity: number }[] = [];
100
99
  for (const h of holdings as Record<string, unknown>[]) {
@@ -105,13 +104,14 @@ export async function prepareDepositHoldings(
105
104
 
106
105
  const createArg = createdEvent.createArgument as Record<string, unknown> | undefined;
107
106
 
108
- // Skip asset types that don't match the requested symbol
109
- const instrument = createArg?.instrument as Record<string, unknown> | undefined;
110
- const createdAssetId = (instrument?.id as string) || (isAmulet ? "Amulet" : null);
111
- if (createdAssetId !== assetId) continue;
112
-
113
- // Skip locked utility holdings
114
- if (!isAmulet && createArg?.lock) continue;
107
+ // Utility holdings: filter by asset type and drop locked ones.
108
+ // Amulet holdings come pre-filtered by template, so no further checks needed.
109
+ if (!isAmulet) {
110
+ if (createArg?.lock) continue;
111
+ const onChainId = (createArg?.instrument as Record<string, unknown> | undefined)?.id as string;
112
+ const heldSymbol = instrumentIdToSymbol[onChainId] || onChainId;
113
+ if (heldSymbol !== assetId) continue;
114
+ }
115
115
 
116
116
  const amountField = createArg?.amount as Record<string, unknown> | string | undefined;
117
117
  const quantity = isAmulet
@@ -323,7 +323,7 @@ export async function depositFunds(
323
323
  amount: parseFloat(parseFloat(String(amount)).toFixed(10)).toString(),
324
324
  instrumentId: {
325
325
  admin: admin,
326
- id: assetId,
326
+ id: resolveOnChainInstrumentId(assetId),
327
327
  },
328
328
  meta: { values: {} },
329
329
  },