@temple-digital-group/temple-canton-js 1.0.35 → 1.0.36

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
@@ -98,9 +98,34 @@ const command = await createOrderProposal(orderArgs, true, walletProvider);
98
98
  ```javascript
99
99
  import { getUserBalances } from '@temple-digital-group/temple-canton-js';
100
100
 
101
+ // Custom Validator
101
102
  const balances = await getUserBalances(partyId);
103
+
104
+ // Wallet Provider
105
+ const balances = await getUserBalances(partyId, walletProvider);
102
106
  ```
103
107
 
108
+ Each entry in the returned array contains:
109
+
110
+ ```javascript
111
+ {
112
+ asset: 'USDCx', // Catalog symbol
113
+ total_balance: 170.5, // Unlocked + locked combined
114
+ available_balance: 150.5, // Unlocked balance (usable for orders)
115
+ locked_balance: 20.0, // Locked balance
116
+ dso: null, // DSO party (Amulet only)
117
+ registrar: '...', // Registrar party (utility tokens)
118
+ operator: '...', // Operator party
119
+ provider: '...', // Provider party
120
+ merge_warning: true, // true if multiple unlocked holdings exist
121
+ holdings: [...], // Unlocked holding contracts
122
+ locked_holdings: [...], // Locked holding contracts
123
+ utilityContext: { ... } // CIP-56 context (null when using wallet provider)
124
+ }
125
+ ```
126
+
127
+ > **Note:** `utilityContext` is only resolved when using a custom validator (ledger API access). When using a wallet provider, it will be `null`.
128
+
104
129
  ### Create an Order
105
130
 
106
131
  ```javascript
@@ -187,11 +212,11 @@ These functions are synchronous and work in all environments (no ledger or provi
187
212
 
188
213
  | Function | Provider | Description |
189
214
  |----------|----------|-------------|
190
- | `getAmuletHoldingsForParty(party)` | **W** | Get Amulet holdings |
191
- | `getUtilityHoldingsForParty(party)` | **W** | Get utility token holdings |
215
+ | `getAmuletHoldingsForParty(party, returnCommand, provider)` | **W** | Get Amulet holdings |
216
+ | `getLockedAmuletHoldingsForParty(party, returnCommand, provider)` | **W** | Get locked Amulet holdings |
217
+ | `getUtilityHoldingsForParty(party, returnCommand, provider)` | **W** | Get utility token holdings |
192
218
  | `getCandidateHoldingForOrderCreation(party, isAmulet, quantity, assetId)` | **W** | Find a holding suitable for an order |
193
- | `getUserBalances(party)` | | Get all balances for a party, grouped by asset |
194
- | `getLockedAmuletHoldingsForParty(party)` | | Get locked Amulet holdings |
219
+ | `getUserBalances(party, provider)` | **W** | Get all balances grouped by asset (Amulet, locked Amulet, and utility) |
195
220
 
196
221
  ### Orders
197
222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temple-digital-group/temple-canton-js",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
4
4
  "description": "JavaScript library for interacting with Temple Canton blockchain",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -26,6 +26,7 @@ import {
26
26
  InstrumentKindAmulet,
27
27
  RegistrarInternalScheme,
28
28
  instrumentCatalog,
29
+ instrumentIdToSymbol,
29
30
  supportedTradingPairs,
30
31
  supportedSymbols
31
32
  } from "./instrumentCatalog.js";
@@ -1943,12 +1944,26 @@ export async function getAmuletHoldingsForParty(party, returnCommand = false, pr
1943
1944
  }
1944
1945
 
1945
1946
  /**
1946
- * Get locked Amulet holdings for a party from the ledger.
1947
+ * Get locked Amulet holdings for a party from the ledger or a provider.
1947
1948
  * @param {string} party - The party ID
1948
1949
  * @param {boolean} [returnCommand=false] - If true, returns the raw command/endpoint instead of executing
1950
+ * @param {Object|null} [provider=null] - Optional external provider to use instead of ledger API
1949
1951
  * @returns {Promise<Object|null>} Locked holdings data, or null on failure
1950
1952
  */
1951
- export async function getLockedAmuletHoldingsForParty(party, returnCommand = false) {
1953
+ export async function getLockedAmuletHoldingsForParty(party, returnCommand = false, provider = null) {
1954
+ // When using provider, skip ledger API calls entirely
1955
+ if (provider) {
1956
+ const args = {
1957
+ templateId: '#splice-amulet:Splice.Amulet:LockedAmulet'
1958
+ };
1959
+ try {
1960
+ return await provider.getActiveContracts(args);
1961
+ } catch (error) {
1962
+ console.error(`Error fetching locked Amulet holdings via provider: ${error}`);
1963
+ return null;
1964
+ }
1965
+ }
1966
+
1952
1967
  const ledgerEnd = await getLedgerEnd();
1953
1968
  if (ledgerEnd === null) {
1954
1969
  console.error(
@@ -3857,20 +3872,21 @@ export async function unlockLockedAmulets(party, returnCommand = false) {
3857
3872
  * Get all balances for a party, grouped by asset type.
3858
3873
  * Returns Amulet, locked Amulet, and utility token holdings with totals.
3859
3874
  * @param {string} party - The party ID
3875
+ * @param {Object|null} [provider=null] - Optional wallet provider to use instead of ledger API
3860
3876
  * @returns {Promise<Object>} Balances grouped by registrar and asset, each with total_balance, holdings, etc.
3861
3877
  */
3862
- export async function getUserBalances(party) {
3863
- // Get all supported instrument IDs from the catalog
3864
- const supportedInstruments = new Set(Object.keys(instrumentCatalog));
3865
-
3878
+ export async function getUserBalances(party, provider = null) {
3866
3879
  const promises = [
3867
- getAmuletHoldingsForParty(party),
3868
- getLockedAmuletHoldingsForParty(party),
3869
- getUtilityHoldingsForParty(party)
3880
+ getAmuletHoldingsForParty(party, false, provider),
3881
+ getLockedAmuletHoldingsForParty(party, false, provider),
3882
+ getUtilityHoldingsForParty(party, false, provider)
3870
3883
  ];
3871
3884
 
3872
3885
  const [amuletHoldings, lockedAmuletHoldings, utilityHoldings] = await Promise.all(promises);
3873
3886
 
3887
+ // Get all supported instrument IDs from the catalog
3888
+ const supportedInstruments = new Set(Object.keys(instrumentCatalog));
3889
+
3874
3890
  // Structure: assetsByKey[registrar][asset] = { ... }
3875
3891
  const assetsByKey = {};
3876
3892
 
@@ -3881,8 +3897,8 @@ export async function getUserBalances(party) {
3881
3897
  }
3882
3898
  if (!assetsByKey[key][asset]) {
3883
3899
  assetsByKey[key][asset] = {
3884
- total_balance: 0,
3885
- total_locked_balance: 0,
3900
+ available_balance: 0,
3901
+ locked_balance: 0,
3886
3902
  holdings: [],
3887
3903
  locked_holdings: [],
3888
3904
  registrar: metadata.registrar || null,
@@ -3905,19 +3921,16 @@ export async function getUserBalances(party) {
3905
3921
  const synchronizerId = holdingData.contractEntry?.JsActiveContract?.synchronizerId || null;
3906
3922
  const disclosure = createdEvent.createdEventBlob || null;
3907
3923
 
3908
- // Only include if Amulet is a supported instrument
3909
- if (!supportedInstruments.has("Amulet")) continue;
3910
-
3911
3924
  const catalogEntry = instrumentCatalog["Amulet"];
3912
3925
  const key = dso; // For Amulet, use DSO as the key
3913
3926
  const entry = initAssetEntry(key, "Amulet", {
3914
- registrar: catalogEntry.registrar || dso,
3927
+ registrar: catalogEntry?.registrar || dso,
3915
3928
  dso: dso,
3916
- operator: catalogEntry.operator,
3917
- provider: catalogEntry.provider
3929
+ operator: catalogEntry?.operator || null,
3930
+ provider: catalogEntry?.provider || null
3918
3931
  });
3919
3932
 
3920
- entry.total_balance += quantity;
3933
+ entry.available_balance += quantity;
3921
3934
  entry.holdings.push({
3922
3935
  contractId: createdEvent.contractId,
3923
3936
  quantity: quantity,
@@ -3931,7 +3944,7 @@ export async function getUserBalances(party) {
3931
3944
  for (const holdingData of lockedAmuletHoldings || []) {
3932
3945
  const createdEvent = holdingData.contractEntry?.JsActiveContract?.createdEvent;
3933
3946
  if (!createdEvent) continue;
3934
-
3947
+
3935
3948
  const quantity = parseFloat(createdEvent.createArgument?.amulet?.amount?.initialAmount || 0);
3936
3949
  const dso = createdEvent.createArgument?.amulet?.dso || "Unknown DSO";
3937
3950
  const synchronizerId = holdingData.contractEntry?.JsActiveContract?.synchronizerId || null;
@@ -3939,19 +3952,16 @@ export async function getUserBalances(party) {
3939
3952
  const lockHolder = createdEvent.createArgument?.lock?.holders?.[0] || null;
3940
3953
  const lockExpiry = createdEvent.createArgument?.lock?.expiresAt || null;
3941
3954
 
3942
- // Only include if Amulet is a supported instrument
3943
- if (!supportedInstruments.has("Amulet")) continue;
3944
-
3945
3955
  const catalogEntry = instrumentCatalog["Amulet"];
3946
3956
  const key = dso;
3947
3957
  const entry = initAssetEntry(key, "Amulet", {
3948
- registrar: catalogEntry.registrar || dso,
3958
+ registrar: catalogEntry?.registrar || dso,
3949
3959
  dso: dso,
3950
- operator: catalogEntry.operator,
3951
- provider: catalogEntry.provider
3960
+ operator: catalogEntry?.operator || null,
3961
+ provider: catalogEntry?.provider || null
3952
3962
  });
3953
3963
 
3954
- entry.total_locked_balance += quantity;
3964
+ entry.locked_balance += quantity;
3955
3965
  entry.locked_holdings.push({
3956
3966
  contractId: createdEvent.contractId,
3957
3967
  quantity: quantity,
@@ -3970,11 +3980,16 @@ export async function getUserBalances(party) {
3970
3980
 
3971
3981
  const quantity = parseFloat(createdEvent.createArgument?.amount || 0);
3972
3982
  let asset = createdEvent.createArgument?.instrument?.id || "Unknown Utility";
3983
+ // Resolve on-chain instrument ID to catalog symbol (e.g. UUID -> "SBC")
3984
+ asset = instrumentIdToSymbol[asset] || asset;
3973
3985
  // Normalize "Amulet" utility tokens to "tAmulet" to distinguish from native Amulet
3974
3986
  if (asset === "Amulet") {
3975
3987
  asset = "tAmulet";
3976
3988
  }
3977
-
3989
+
3990
+ // Only include supported instruments
3991
+ if (!supportedInstruments.has(asset)) continue;
3992
+
3978
3993
  const registrar = createdEvent.createArgument?.instrument?.source || "Unknown Registrar";
3979
3994
  const synchronizerId = holdingData.contractEntry?.JsActiveContract?.synchronizerId || null;
3980
3995
  const disclosure = createdEvent.createdEventBlob || null;
@@ -3985,9 +4000,6 @@ export async function getUserBalances(party) {
3985
4000
  const lockHolder = lockData?.holders?.[0] || null;
3986
4001
  const lockExpiry = lockData?.expiresAt || null;
3987
4002
 
3988
- // Only include if this is a supported instrument
3989
- if (!supportedInstruments.has(asset)) continue;
3990
-
3991
4003
  const catalogEntry = instrumentCatalog[asset];
3992
4004
  const key = registrar;
3993
4005
  const entry = initAssetEntry(key, asset, {
@@ -3998,7 +4010,7 @@ export async function getUserBalances(party) {
3998
4010
  });
3999
4011
 
4000
4012
  if (isLocked) {
4001
- entry.total_locked_balance += quantity;
4013
+ entry.locked_balance += quantity;
4002
4014
  entry.locked_holdings.push({
4003
4015
  contractId: createdEvent.contractId,
4004
4016
  quantity: quantity,
@@ -4009,7 +4021,7 @@ export async function getUserBalances(party) {
4009
4021
  lockExpiry
4010
4022
  });
4011
4023
  } else {
4012
- entry.total_balance += quantity;
4024
+ entry.available_balance += quantity;
4013
4025
  entry.holdings.push({
4014
4026
  contractId: createdEvent.contractId,
4015
4027
  quantity: quantity,
@@ -4020,49 +4032,53 @@ export async function getUserBalances(party) {
4020
4032
  }
4021
4033
  }
4022
4034
 
4023
- // Resolve utility contexts for non-Amulet assets
4024
- const utilityContextPromises = new Map();
4025
- for (const key of Object.keys(assetsByKey)) {
4026
- for (const asset of Object.keys(assetsByKey[key])) {
4027
- if (asset === "Amulet") {
4028
- continue;
4029
- }
4030
- const assetEntry = assetsByKey[key][asset];
4031
- if (assetEntry.utilityContext) {
4032
- continue;
4033
- }
4034
- const registrar = assetEntry.registrar || key;
4035
- const cacheKey = `${registrar || "*"}::${asset}`;
4036
- if (!utilityContextPromises.has(cacheKey)) {
4037
- utilityContextPromises.set(cacheKey, resolveUtilityCip56Context({
4038
- party,
4039
- registrar,
4040
- utilityAsset: asset
4041
- }));
4035
+ // Only resolve utility contexts when NOT using a wallet provider
4036
+ // (resolveUtilityCip56Context requires ledger API access)
4037
+ if (!provider) {
4038
+ // Resolve utility contexts for non-Amulet assets
4039
+ const utilityContextPromises = new Map();
4040
+ for (const key of Object.keys(assetsByKey)) {
4041
+ for (const asset of Object.keys(assetsByKey[key])) {
4042
+ if (asset === "Amulet") {
4043
+ continue;
4044
+ }
4045
+ const assetEntry = assetsByKey[key][asset];
4046
+ if (assetEntry.utilityContext) {
4047
+ continue;
4048
+ }
4049
+ const registrar = assetEntry.registrar || key;
4050
+ const cacheKey = `${registrar || "*"}::${asset}`;
4051
+ if (!utilityContextPromises.has(cacheKey)) {
4052
+ utilityContextPromises.set(cacheKey, resolveUtilityCip56Context({
4053
+ party,
4054
+ registrar,
4055
+ utilityAsset: asset
4056
+ }));
4057
+ }
4042
4058
  }
4043
4059
  }
4044
- }
4045
4060
 
4046
- // Await all utility context promises
4047
- for (const [key, promise] of utilityContextPromises.entries()) {
4048
- utilityContextPromises.set(key, await promise);
4049
- }
4061
+ // Await all utility context promises
4062
+ for (const [key, promise] of utilityContextPromises.entries()) {
4063
+ utilityContextPromises.set(key, await promise);
4064
+ }
4050
4065
 
4051
- // Assign utility contexts
4052
- for (const key of Object.keys(assetsByKey)) {
4053
- for (const asset of Object.keys(assetsByKey[key])) {
4054
- if (asset === "Amulet") {
4055
- continue;
4056
- }
4057
- const assetEntry = assetsByKey[key][asset];
4058
- if (assetEntry.utilityContext) {
4059
- continue;
4060
- }
4061
- const registrar = assetEntry.registrar || key;
4062
- const cacheKey = `${registrar || "*"}::${asset}`;
4063
- assetEntry.utilityContext = utilityContextPromises.get(cacheKey) || null;
4064
- if (!assetEntry.registrar && assetEntry.utilityContext?.registrar) {
4065
- assetEntry.registrar = assetEntry.utilityContext.registrar;
4066
+ // Assign utility contexts
4067
+ for (const key of Object.keys(assetsByKey)) {
4068
+ for (const asset of Object.keys(assetsByKey[key])) {
4069
+ if (asset === "Amulet") {
4070
+ continue;
4071
+ }
4072
+ const assetEntry = assetsByKey[key][asset];
4073
+ if (assetEntry.utilityContext) {
4074
+ continue;
4075
+ }
4076
+ const registrar = assetEntry.registrar || key;
4077
+ const cacheKey = `${registrar || "*"}::${asset}`;
4078
+ assetEntry.utilityContext = utilityContextPromises.get(cacheKey) || null;
4079
+ if (!assetEntry.registrar && assetEntry.utilityContext?.registrar) {
4080
+ assetEntry.registrar = assetEntry.utilityContext.registrar;
4081
+ }
4066
4082
  }
4067
4083
  }
4068
4084
  }
@@ -4074,8 +4090,9 @@ export async function getUserBalances(party) {
4074
4090
  const assetEntry = assetsByKey[key][asset];
4075
4091
  balances.push({
4076
4092
  asset: asset,
4077
- total_balance: assetEntry.total_balance,
4078
- total_locked_balance: assetEntry.total_locked_balance,
4093
+ total_balance: assetEntry.available_balance + assetEntry.locked_balance,
4094
+ available_balance: assetEntry.available_balance,
4095
+ locked_balance: assetEntry.locked_balance,
4079
4096
  dso: assetEntry.dso,
4080
4097
  registrar: assetEntry.registrar,
4081
4098
  operator: assetEntry.operator,
@@ -132,9 +132,32 @@ export const instrumentCatalog = {
132
132
  eventBlob: "CgMyLjESigcKRQBctH85ukdhXkql3QKxd2PMDtoDCHrmye6OOOgsE0YqrsoREiAtJTdNPPLAo2qNBxCDbU4BocW3NHP0W2qBACxdqoygyxITdXRpbGl0eS1yZWdpc3RyeS12MBqLAQpAZWQ3M2Q1YjlhYjcxNzMzM2YzZGJkMTIyZGU3YmUzMTU2ZjhiZjI2MTRhNjczNjBjM2RkNjFmYzAxMzUxMzNmYRIHVXRpbGl0eRIIUmVnaXN0cnkSAlYwEg1Db25maWd1cmF0aW9uEglBcHBSZXdhcmQaFkFwcFJld2FyZENvbmZpZ3VyYXRpb24iqgNqpwMKbApqOmhhdXRoMF8wMDdjNjY0MzUzOGYyZWFkZDNlNTczZGQwNWI5OjoxMjIwNWJjYzEwNmVmYTBlYWE3ZjE4ZGM0OTFlNWM2ZjVmYjliMGNjNjhkYzExMGFlNjZmNGVkNjQ2NzQ3NWQ3Yzc4ZQpWClQ6UmNidGMtbmV0d29yazo6MTIyMDVhZjNiOTQ5YTA0Nzc2ZmM0OGNkY2MwNWEwNjBmNmJkYTJlNDcwNjMyOTM1ZjM3NWQxMDQ5YTg1NDZhM2IyNjIK3gEK2wFq2AEKTQpLOklEU086OjEyMjBiMTQzMWVmMjE3MzQyZGI0NGQ1MTZiYjliZWZkZTgwMmJlN2Q4ODk5NjM3ZDI5MDg5NWZhNTg4ODBmMTlhY2NjCoYBCoMBaoABCmwKajpoYXV0aDBfMDA3YzY3ZGM3YzRhZmJmNWJjMzdmOTgwNWUzMTo6MTIyMDViY2MxMDZlZmEwZWFhN2YxOGRjNDkxZTVjNmY1ZmI5YjBjYzY4ZGMxMTBhZTY2ZjRlZDY0Njc0NzVkN2M3OGUKEAoOMgwwLjIwMDAwMDAwMDAqaGF1dGgwXzAwN2M2NjQzNTM4ZjJlYWRkM2U1NzNkZDA1Yjk6OjEyMjA1YmNjMTA2ZWZhMGVhYTdmMThkYzQ5MWU1YzZmNWZiOWIwY2M2OGRjMTEwYWU2NmY0ZWQ2NDY3NDc1ZDdjNzhlMlJjYnRjLW5ldHdvcms6OjEyMjA1YWYzYjk0OWEwNDc3NmZjNDhjZGNjMDVhMDYwZjZiZGEyZTQ3MDYzMjkzNWYzNzVkMTA0OWE4NTQ2YTNiMjYyOSqKuWddQgYAQioKJgokCAESIP6u2ZhXB5fXuwvdDvhv+mn6JPF20D2scQdCh/yRSuNsEB4="
133
133
  }
134
134
  }
135
- }
135
+ },
136
+
137
+ // SBC (Stablecoin) -- used for balance reporting, but not a tradable instrument itself
138
+ "SBC": {
139
+ id: "f29bdd7a-1469-498a-ba2a-796bf5387b31",
140
+ scheme: RegistrarInternalScheme,
141
+ kind: InstrumentKindUtility,
142
+ disclosureURL: null,
143
+ requireCredentials: false,
144
+ mainnet: {
145
+ synchronizerId: "global-domain::1220b1431ef217342db44d516bb9befde802be7d8899637d290895fa58880f19accc",
146
+ registrar: "party-28dc4516-b5ca-44ff-86c7-2107e90a6807::1220b8301e18aa8a401d6e34e6c20f8b0243183c514373bca8f1b6b9270246341a9e",
147
+ operator: "auth0_007c6643538f2eadd3e573dd05b9::12205bcc106efa0eaa7f18dc491e5c6f5fb9b0cc68dc110ae66f4ed6467475d7c78e",
148
+ provider: "party-28dc4516-b5ca-44ff-86c7-2107e90a6807::1220b8301e18aa8a401d6e34e6c20f8b0243183c514373bca8f1b6b9270246341a9e",
149
+ }
150
+ },
136
151
  };
137
152
 
153
+ // Reverse lookup: maps on-chain instrument IDs to catalog keys
154
+ // (for instruments where the on-chain ID differs from the catalog key, e.g. UUID -> "SBC")
155
+ export const instrumentIdToSymbol = Object.fromEntries(
156
+ Object.entries(instrumentCatalog)
157
+ .filter(([key, entry]) => entry.id && entry.id !== key)
158
+ .map(([key, entry]) => [entry.id, key])
159
+ );
160
+
138
161
  // Supported trading pairs (like Go's supportedTradingPairs)
139
162
  export const supportedTradingPairs = [
140
163
  { baseAsset: "Amulet", quoteAsset: "USDCx" },