@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 +39 -37
- package/dist/canton/deposits.js +12 -10
- package/dist/canton/withdrawals.d.ts +0 -20
- package/dist/canton/withdrawals.js +0 -265
- package/package.json +1 -1
- package/src/canton/deposits.ts +10 -10
- package/src/canton/index.js +370 -14
- package/src/canton/instrumentCatalog.d.ts +1 -0
- package/src/canton/instrumentCatalog.js +137 -37
- package/src/canton/withdrawals.ts +0 -299
package/src/canton/index.js
CHANGED
|
@@ -45,7 +45,7 @@ import unlockAmuletSchema from "./request_schemas/unlock_amulet.json" with { typ
|
|
|
45
45
|
import createUtilityCredentialSchema from "./request_schemas/create_utility_credential.json" with { type: "json" };
|
|
46
46
|
|
|
47
47
|
// Import instrument catalog
|
|
48
|
-
import { RegistrarInternalScheme, instrumentCatalog, instrumentIdToSymbol, supportedTradingPairs, supportedSymbols, normalizeAssetId } from "./instrumentCatalog.js";
|
|
48
|
+
import { RegistrarInternalScheme, instrumentCatalog, instrumentIdToSymbol, supportedTradingPairs, supportedSymbols, normalizeAssetId, resolveOnChainInstrumentId } from "./instrumentCatalog.js";
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -1233,7 +1233,7 @@ async function buildLocalUtilityTransferFactory(investor, registrar, amount, hol
|
|
|
1233
1233
|
amount: amount,
|
|
1234
1234
|
instrumentId: {
|
|
1235
1235
|
admin: registrar,
|
|
1236
|
-
id: utilityAsset || "tSBC",
|
|
1236
|
+
id: resolveOnChainInstrumentId(utilityAsset) || "tSBC",
|
|
1237
1237
|
},
|
|
1238
1238
|
},
|
|
1239
1239
|
expectedAdmin: registrar,
|
|
@@ -1270,7 +1270,7 @@ async function getTransferFactoryFromRegistry(investor, registrar, amount, holdi
|
|
|
1270
1270
|
dso: registrar,
|
|
1271
1271
|
amount: amount,
|
|
1272
1272
|
holding_ids: holdingIds,
|
|
1273
|
-
instrument_id: utilityAsset,
|
|
1273
|
+
instrument_id: resolveOnChainInstrumentId(utilityAsset),
|
|
1274
1274
|
time: new Date().toISOString(),
|
|
1275
1275
|
timeEnd: new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
1276
1276
|
};
|
|
@@ -1315,7 +1315,7 @@ export async function getTransferFactory(investor, admin, amount, holdingIds, re
|
|
|
1315
1315
|
dso: admin,
|
|
1316
1316
|
amount: amount,
|
|
1317
1317
|
holding_ids: holdingIds,
|
|
1318
|
-
instrument_id: utilityAsset || "Amulet",
|
|
1318
|
+
instrument_id: resolveOnChainInstrumentId(utilityAsset) || "Amulet",
|
|
1319
1319
|
time: new Date().toISOString(),
|
|
1320
1320
|
timeEnd: new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
1321
1321
|
};
|
|
@@ -1627,8 +1627,9 @@ export async function getUtxoCount(party, assetId, provider = null) {
|
|
|
1627
1627
|
quantity = parseFloat(createArg?.amount?.initialAmount || 0);
|
|
1628
1628
|
matchesAsset = true;
|
|
1629
1629
|
} else {
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1630
|
+
const onChainId = createArg?.instrument?.id || null;
|
|
1631
|
+
const heldSymbol = instrumentIdToSymbol[onChainId] || onChainId;
|
|
1632
|
+
if (heldSymbol !== assetId) continue;
|
|
1632
1633
|
isLocked = !!createArg?.lock;
|
|
1633
1634
|
quantity = parseFloat(createArg?.amount || 0);
|
|
1634
1635
|
matchesAsset = true;
|
|
@@ -1818,8 +1819,12 @@ export async function getCandidateHoldingForOrderCreation(party, isAmulet, requi
|
|
|
1818
1819
|
// For Utility, quantity is in createArgument.amount
|
|
1819
1820
|
const quantity = isAmulet ? parseFloat(createdEvent.createArgument?.amount?.initialAmount || 0) : parseFloat(createdEvent.createArgument?.amount || 0);
|
|
1820
1821
|
|
|
1821
|
-
// For Utility, also get the asset ID and scheme
|
|
1822
|
-
|
|
1822
|
+
// For Utility, also get the asset ID and scheme.
|
|
1823
|
+
// instrument.id from the ledger is the on-chain value (UUID for USDA/SBC);
|
|
1824
|
+
// translate to catalog symbol so downstream comparisons against user-passed
|
|
1825
|
+
// expectedAssetId work uniformly.
|
|
1826
|
+
const onChainId = isAmulet ? null : createdEvent.createArgument?.instrument?.id || null;
|
|
1827
|
+
const asset = isAmulet ? "Amulet" : instrumentIdToSymbol[onChainId] || onChainId;
|
|
1823
1828
|
const instrumentScheme = isAmulet ? null : createdEvent.createArgument?.instrument?.scheme || null;
|
|
1824
1829
|
|
|
1825
1830
|
// Extract owner/holder/operator/provider for debugging
|
|
@@ -2006,8 +2011,8 @@ export async function mergeAmuletHoldingsForParty(party, returnCommand = false,
|
|
|
2006
2011
|
return null;
|
|
2007
2012
|
}
|
|
2008
2013
|
|
|
2009
|
-
// Sort by quantity
|
|
2010
|
-
contracts.sort((a, b) =>
|
|
2014
|
+
// Sort by quantity descending (largest first) and apply maxUtxos limit
|
|
2015
|
+
contracts.sort((a, b) => b.quantity - a.quantity);
|
|
2011
2016
|
const selectedContracts = maxUtxos && maxUtxos < contracts.length ? contracts.slice(0, maxUtxos) : contracts;
|
|
2012
2017
|
|
|
2013
2018
|
if (selectedContracts.length < 2) {
|
|
@@ -2209,7 +2214,8 @@ export async function mergeUtilityHoldingsForParty(party, utilityAsset, returnCo
|
|
|
2209
2214
|
const source = createArg?.instrument?.source || null;
|
|
2210
2215
|
const rawAmount = createArg?.amount || "0";
|
|
2211
2216
|
const quantity = parseFloat(rawAmount);
|
|
2212
|
-
const
|
|
2217
|
+
const onChainId = createArg?.instrument?.id || null;
|
|
2218
|
+
const asset = instrumentIdToSymbol[onChainId] || onChainId;
|
|
2213
2219
|
const lock = createArg?.lock || false;
|
|
2214
2220
|
// Extract the holder from the holding contract (for utility tokens)
|
|
2215
2221
|
// Try multiple possible field names
|
|
@@ -2312,9 +2318,9 @@ export async function mergeUtilityHoldingsForParty(party, utilityAsset, returnCo
|
|
|
2312
2318
|
const finalHolders = [...new Set(contracts.map((c) => c.holder).filter(Boolean))];
|
|
2313
2319
|
const holder = finalHolders[0] || party; // Use the holder from filtered holdings
|
|
2314
2320
|
|
|
2315
|
-
// Sort
|
|
2321
|
+
// Sort descending by quantity so largest UTXOs are merged first
|
|
2316
2322
|
if (maxUtxos) {
|
|
2317
|
-
contracts.sort((a, b) =>
|
|
2323
|
+
contracts.sort((a, b) => b.quantity - a.quantity);
|
|
2318
2324
|
if (contracts.length > maxUtxos) {
|
|
2319
2325
|
contracts.length = maxUtxos;
|
|
2320
2326
|
}
|
|
@@ -2368,7 +2374,7 @@ export async function mergeUtilityHoldingsForParty(party, utilityAsset, returnCo
|
|
|
2368
2374
|
const rawAmount = transferInstruction.amount || totalQuantity;
|
|
2369
2375
|
const actualAmount = parseFloat(parseFloat(rawAmount).toFixed(10));
|
|
2370
2376
|
const instrumentAdmin = transferInstruction.instrumentId?.admin || registrar;
|
|
2371
|
-
const instrumentIdValue = transferInstruction.instrumentId?.id || utilityAsset;
|
|
2377
|
+
const instrumentIdValue = transferInstruction.instrumentId?.id || resolveOnChainInstrumentId(utilityAsset);
|
|
2372
2378
|
// For utility tokens the TransferFactory is registrar-administered, so expectedAdmin = registrar.
|
|
2373
2379
|
// Prefer the value returned by the Registry API, then fall back to the registrar from holdings.
|
|
2374
2380
|
const expectedAdmin = transferFactoryData.expectedAdmin || registrar;
|
|
@@ -2645,6 +2651,185 @@ export { deposit, prepareDepositHoldings, depositFunds } from "../../dist/canton
|
|
|
2645
2651
|
// Withdrawal functions moved to src/canton/withdrawals.ts
|
|
2646
2652
|
export { finalizeWithdrawFunds, withdrawFunds, withdrawDelegation } from "../../dist/canton/withdrawals.js";
|
|
2647
2653
|
|
|
2654
|
+
// ─── Emergency Allocation Withdraw ──────────────────────────────────────────
|
|
2655
|
+
|
|
2656
|
+
export async function buildAllocationWithdrawCommand(opts) {
|
|
2657
|
+
const { disclosures, userId } = opts || {};
|
|
2658
|
+
const assetId = normalizeAssetId(opts.assetId);
|
|
2659
|
+
const sender = getAdapterPartyId() || opts.sender || config.VALIDATOR_USER_PARTY_ID;
|
|
2660
|
+
|
|
2661
|
+
if (!opts.allocationCids || !sender || !assetId) {
|
|
2662
|
+
return { error: "buildAllocationWithdrawCommand: allocationCids, sender, and assetId are required" };
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
const cids = Array.isArray(opts.allocationCids) ? opts.allocationCids : [opts.allocationCids];
|
|
2666
|
+
if (cids.length === 0) {
|
|
2667
|
+
return { error: "buildAllocationWithdrawCommand: allocationCids must not be empty" };
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
const isAmulet = assetId === "Amulet";
|
|
2671
|
+
|
|
2672
|
+
// --- Resolve context and disclosures (once for all CIDs) ---
|
|
2673
|
+
let choiceContextData = {};
|
|
2674
|
+
let disclosedContracts = [];
|
|
2675
|
+
|
|
2676
|
+
if (shouldUseLedgerForMetadata()) {
|
|
2677
|
+
if (isAmulet) {
|
|
2678
|
+
const amuletCtx = await resolveAmuletContext({ investor: sender, holdingIds: [], transferAmount: 0 });
|
|
2679
|
+
if (!amuletCtx) {
|
|
2680
|
+
return { error: "buildAllocationWithdrawCommand: failed to resolve Amulet context from ledger" };
|
|
2681
|
+
}
|
|
2682
|
+
const contextKeys = amuletCtx.contextKeys;
|
|
2683
|
+
choiceContextData = {
|
|
2684
|
+
values: {
|
|
2685
|
+
[contextKeys.amuletRules]: { tag: "AV_ContractId", value: amuletCtx.amuletRules.contractCid },
|
|
2686
|
+
[contextKeys.openRound]: { tag: "AV_ContractId", value: amuletCtx.openMiningRound.contractCid },
|
|
2687
|
+
[contextKeys.featuredAppRight]: { tag: "AV_ContractId", value: amuletCtx.featuredAppRight.contractCid },
|
|
2688
|
+
[contextKeys.expireLock]: { tag: "AV_Bool", value: true },
|
|
2689
|
+
},
|
|
2690
|
+
};
|
|
2691
|
+
disclosedContracts = [
|
|
2692
|
+
{ templateId: amuletCtx.amuletRules.templateId || null, contractId: amuletCtx.amuletRules.contractCid, createdEventBlob: amuletCtx.amuletRules.disclosureCid, synchronizerId: amuletCtx.amuletRules.synchronizerId },
|
|
2693
|
+
{ templateId: amuletCtx.openMiningRound.templateId || null, contractId: amuletCtx.openMiningRound.contractCid, createdEventBlob: amuletCtx.openMiningRound.disclosureCid, synchronizerId: amuletCtx.openMiningRound.synchronizerId },
|
|
2694
|
+
{ templateId: amuletCtx.externalAmuletRules.templateId || null, contractId: amuletCtx.externalAmuletRules.contractCid, createdEventBlob: amuletCtx.externalAmuletRules.disclosureCid, synchronizerId: amuletCtx.externalAmuletRules.synchronizerId },
|
|
2695
|
+
];
|
|
2696
|
+
if (amuletCtx.featuredAppRight?.contractCid && amuletCtx.featuredAppRight?.disclosureCid) {
|
|
2697
|
+
disclosedContracts.push({ templateId: amuletCtx.featuredAppRight.templateId || null, contractId: amuletCtx.featuredAppRight.contractCid, createdEventBlob: amuletCtx.featuredAppRight.disclosureCid, synchronizerId: amuletCtx.featuredAppRight.synchronizerId });
|
|
2698
|
+
}
|
|
2699
|
+
} else {
|
|
2700
|
+
const instrumentDef = resolveInstrumentDefinition(assetId);
|
|
2701
|
+
const networkDef = instrumentDef?.[config.NETWORK];
|
|
2702
|
+
const registrar = networkDef?.registrar || getInstrumentRegistrar(assetId) || config.VALIDATOR_REGISTRAR_PARTY_ID;
|
|
2703
|
+
const [allocFactory, instConfig] = await Promise.all([
|
|
2704
|
+
resolveUtilityAllocationFactory(registrar, sender),
|
|
2705
|
+
resolveUtilityInstrumentConfiguration(assetId, registrar),
|
|
2706
|
+
]);
|
|
2707
|
+
if (instConfig) {
|
|
2708
|
+
choiceContextData = {
|
|
2709
|
+
values: {
|
|
2710
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfiguration]: { tag: "AV_ContractId", value: instConfig.contractCid },
|
|
2711
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfigurationPrefixed]: { tag: "AV_ContractId", value: instConfig.contractCid },
|
|
2712
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentials]: { tag: "AV_List", value: [] },
|
|
2713
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentialsPrefixed]: { tag: "AV_List", value: [] },
|
|
2714
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentials]: { tag: "AV_List", value: [] },
|
|
2715
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentialsPrefixed]: { tag: "AV_List", value: [] },
|
|
2716
|
+
[DEFAULT_AMULET_CONTEXT_KEYS.expireLock]: { tag: "AV_Bool", value: true },
|
|
2717
|
+
},
|
|
2718
|
+
};
|
|
2719
|
+
disclosedContracts.push({ templateId: null, contractId: instConfig.contractCid, createdEventBlob: instConfig.disclosureCid, synchronizerId: instConfig.synchronizerId });
|
|
2720
|
+
}
|
|
2721
|
+
if (allocFactory) {
|
|
2722
|
+
disclosedContracts.push({ templateId: null, contractId: allocFactory.contractCid, createdEventBlob: allocFactory.disclosureCid, synchronizerId: allocFactory.synchronizerId });
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
} else if (isAmulet) {
|
|
2726
|
+
// Resolve via disclosures API
|
|
2727
|
+
let resolvedData = disclosures?.disclosures || disclosures || null;
|
|
2728
|
+
if (!resolvedData?.choiceContext) {
|
|
2729
|
+
const disclosuresResult = await getDisclosures(sender);
|
|
2730
|
+
resolvedData = disclosuresResult?.disclosures;
|
|
2731
|
+
if (disclosuresResult?.error || !resolvedData?.choiceContext) {
|
|
2732
|
+
return { error: `buildAllocationWithdrawCommand: failed to resolve Amulet disclosures: ${disclosuresResult?.message || disclosuresResult?.error || "missing choiceContext"}` };
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
choiceContextData = resolvedData.choiceContext.choiceContextData || {};
|
|
2736
|
+
const values = choiceContextData.values;
|
|
2737
|
+
if (values && !values[DEFAULT_AMULET_CONTEXT_KEYS.expireLock]) {
|
|
2738
|
+
values[DEFAULT_AMULET_CONTEXT_KEYS.expireLock] = { tag: "AV_Bool", value: true };
|
|
2739
|
+
}
|
|
2740
|
+
disclosedContracts = (resolvedData.choiceContext.disclosedContracts || []).map(dc => ({
|
|
2741
|
+
templateId: dc.templateId, contractId: dc.contractId, createdEventBlob: dc.createdEventBlob, synchronizerId: dc.synchronizerId,
|
|
2742
|
+
}));
|
|
2743
|
+
} else {
|
|
2744
|
+
// Utility: build context from catalog data (no ledger/validator access needed)
|
|
2745
|
+
const instrumentDef = resolveInstrumentDefinition(assetId);
|
|
2746
|
+
const networkContracts = instrumentDef?.[config.NETWORK];
|
|
2747
|
+
if (!networkContracts) {
|
|
2748
|
+
return { error: `buildAllocationWithdrawCommand: no ${config.NETWORK} config for ${assetId} in instrument catalog` };
|
|
2749
|
+
}
|
|
2750
|
+
const instConfig = networkContracts.instrumentConfiguration;
|
|
2751
|
+
const allocFactory = networkContracts.allocationFactory;
|
|
2752
|
+
const synchronizerId = networkContracts.synchronizerId;
|
|
2753
|
+
|
|
2754
|
+
if (!instConfig?.contractId || !instConfig?.eventBlob) {
|
|
2755
|
+
return { error: `buildAllocationWithdrawCommand: instrumentConfiguration missing for ${assetId} on ${config.NETWORK}` };
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
choiceContextData = {
|
|
2759
|
+
values: {
|
|
2760
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfiguration]: { tag: "AV_ContractId", value: instConfig.contractId },
|
|
2761
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfigurationPrefixed]: { tag: "AV_ContractId", value: instConfig.contractId },
|
|
2762
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentials]: { tag: "AV_List", value: [] },
|
|
2763
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentialsPrefixed]: { tag: "AV_List", value: [] },
|
|
2764
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentials]: { tag: "AV_List", value: [] },
|
|
2765
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentialsPrefixed]: { tag: "AV_List", value: [] },
|
|
2766
|
+
[DEFAULT_AMULET_CONTEXT_KEYS.expireLock]: { tag: "AV_Bool", value: true },
|
|
2767
|
+
},
|
|
2768
|
+
};
|
|
2769
|
+
disclosedContracts.push({
|
|
2770
|
+
templateId: instConfig.templateId || null,
|
|
2771
|
+
contractId: instConfig.contractId,
|
|
2772
|
+
createdEventBlob: instConfig.eventBlob,
|
|
2773
|
+
synchronizerId: synchronizerId,
|
|
2774
|
+
});
|
|
2775
|
+
if (allocFactory?.contractId && allocFactory?.eventBlob) {
|
|
2776
|
+
disclosedContracts.push({
|
|
2777
|
+
templateId: allocFactory.templateId || null,
|
|
2778
|
+
contractId: allocFactory.contractId,
|
|
2779
|
+
createdEventBlob: allocFactory.eventBlob,
|
|
2780
|
+
synchronizerId: synchronizerId,
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
// --- Build one command per allocation CID ---
|
|
2786
|
+
const endpoint = `${config.VALIDATOR_API_URL}/v2/commands/submit-and-wait`;
|
|
2787
|
+
const commands = cids.map(cid => {
|
|
2788
|
+
const allocationCid = normalizeContractId(cid);
|
|
2789
|
+
const command = {
|
|
2790
|
+
commands: [{
|
|
2791
|
+
ExerciseCommand: {
|
|
2792
|
+
templateId: "#splice-api-token-allocation-v1:Splice.Api.Token.AllocationV1:Allocation",
|
|
2793
|
+
contractId: allocationCid,
|
|
2794
|
+
choice: "Allocation_Withdraw",
|
|
2795
|
+
choiceArgument: {
|
|
2796
|
+
extraArgs: {
|
|
2797
|
+
context: choiceContextData,
|
|
2798
|
+
meta: { values: {} },
|
|
2799
|
+
},
|
|
2800
|
+
},
|
|
2801
|
+
},
|
|
2802
|
+
}],
|
|
2803
|
+
commandId: randomUUID(),
|
|
2804
|
+
userId: userId || getUserId() || config.AUTH0_USER_ID || "temple",
|
|
2805
|
+
applicationId: "temple",
|
|
2806
|
+
actAs: [sender],
|
|
2807
|
+
disclosedContracts: [...disclosedContracts],
|
|
2808
|
+
};
|
|
2809
|
+
dedupeDisclosedContracts(command);
|
|
2810
|
+
return { command, endpoint };
|
|
2811
|
+
});
|
|
2812
|
+
|
|
2813
|
+
// Auto-submit via wallet adapter if submit option is true
|
|
2814
|
+
if (opts.submit && getWalletAdapter()) {
|
|
2815
|
+
const results = [];
|
|
2816
|
+
for (const { command } of commands) {
|
|
2817
|
+
try {
|
|
2818
|
+
const txResult = await submitCommand(command);
|
|
2819
|
+
results.push({ success: true, commandId: command.commandId, result: txResult });
|
|
2820
|
+
} catch (error) {
|
|
2821
|
+
results.push({ success: false, commandId: command.commandId, error: error.message });
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
return { commands, results };
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
if (opts.submit && !getWalletAdapter()) {
|
|
2828
|
+
return { error: "buildAllocationWithdrawCommand: submit requires a wallet adapter. Call setWalletAdapter() first." };
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
return { commands };
|
|
2832
|
+
}
|
|
2648
2833
|
|
|
2649
2834
|
// ─── Onboarding & Delegation ─────────────────────────────────────────────────
|
|
2650
2835
|
|
|
@@ -2832,6 +3017,177 @@ export async function isUserOnboarded(user = null, returnCommand = false) {
|
|
|
2832
3017
|
}
|
|
2833
3018
|
}
|
|
2834
3019
|
|
|
3020
|
+
/**
|
|
3021
|
+
* Build an Allocation_Withdraw ExerciseCommand for one or more stuck allocation CIDs.
|
|
3022
|
+
* Returns the command(s) ready for submission via wallet adapter or ledger API.
|
|
3023
|
+
*
|
|
3024
|
+
* @param {Object} opts
|
|
3025
|
+
* @param {string|string[]} opts.allocationCids - Single CID or array of allocation contract IDs
|
|
3026
|
+
* @param {string} opts.assetId - The asset symbol ("Amulet", "CC", "USDCx", etc.)
|
|
3027
|
+
* @param {string} [opts.sender] - Party ID. Falls back to wallet adapter or VALIDATOR_USER_PARTY_ID.
|
|
3028
|
+
* @param {Object} [opts.disclosures] - Pre-fetched disclosures (optional, will be resolved if missing)
|
|
3029
|
+
* @returns {Promise<Object>} { commands: [...] } array of { command, endpoint } objects, or { error }
|
|
3030
|
+
*/
|
|
3031
|
+
export async function buildAllocationWithdrawCommand(opts) {
|
|
3032
|
+
const { disclosures, userId } = opts || {};
|
|
3033
|
+
const assetId = (opts.assetId === "CC" || opts.assetId === "cc") ? "Amulet" : opts.assetId;
|
|
3034
|
+
const sender = getAdapterPartyId() || opts.sender || config.VALIDATOR_USER_PARTY_ID;
|
|
3035
|
+
|
|
3036
|
+
if (!opts.allocationCids || !sender || !assetId) {
|
|
3037
|
+
return { error: "buildAllocationWithdrawCommand: allocationCids, sender, and assetId are required" };
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
const cids = Array.isArray(opts.allocationCids) ? opts.allocationCids : [opts.allocationCids];
|
|
3041
|
+
if (cids.length === 0) {
|
|
3042
|
+
return { error: "buildAllocationWithdrawCommand: allocationCids must not be empty" };
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
const isAmulet = assetId === "Amulet";
|
|
3046
|
+
|
|
3047
|
+
// --- Resolve context and disclosures (once for all CIDs) ---
|
|
3048
|
+
let choiceContextData = {};
|
|
3049
|
+
let disclosedContracts = [];
|
|
3050
|
+
|
|
3051
|
+
if (shouldUseLedgerForMetadata()) {
|
|
3052
|
+
if (isAmulet) {
|
|
3053
|
+
const amuletCtx = await resolveAmuletContext({ investor: sender, holdingIds: [], transferAmount: 0 });
|
|
3054
|
+
if (!amuletCtx) {
|
|
3055
|
+
return { error: "buildAllocationWithdrawCommand: failed to resolve Amulet context from ledger" };
|
|
3056
|
+
}
|
|
3057
|
+
const contextKeys = amuletCtx.contextKeys;
|
|
3058
|
+
choiceContextData = {
|
|
3059
|
+
values: {
|
|
3060
|
+
[contextKeys.amuletRules]: { tag: "AV_ContractId", value: amuletCtx.amuletRules.contractCid },
|
|
3061
|
+
[contextKeys.openRound]: { tag: "AV_ContractId", value: amuletCtx.openMiningRound.contractCid },
|
|
3062
|
+
[contextKeys.featuredAppRight]: { tag: "AV_ContractId", value: amuletCtx.featuredAppRight.contractCid },
|
|
3063
|
+
[contextKeys.expireLock]: { tag: "AV_Bool", value: true },
|
|
3064
|
+
},
|
|
3065
|
+
};
|
|
3066
|
+
disclosedContracts = [
|
|
3067
|
+
{ templateId: amuletCtx.amuletRules.templateId || null, contractId: amuletCtx.amuletRules.contractCid, createdEventBlob: amuletCtx.amuletRules.disclosureCid, synchronizerId: amuletCtx.amuletRules.synchronizerId },
|
|
3068
|
+
{ templateId: amuletCtx.openMiningRound.templateId || null, contractId: amuletCtx.openMiningRound.contractCid, createdEventBlob: amuletCtx.openMiningRound.disclosureCid, synchronizerId: amuletCtx.openMiningRound.synchronizerId },
|
|
3069
|
+
{ templateId: amuletCtx.externalAmuletRules.templateId || null, contractId: amuletCtx.externalAmuletRules.contractCid, createdEventBlob: amuletCtx.externalAmuletRules.disclosureCid, synchronizerId: amuletCtx.externalAmuletRules.synchronizerId },
|
|
3070
|
+
];
|
|
3071
|
+
if (amuletCtx.featuredAppRight?.contractCid && amuletCtx.featuredAppRight?.disclosureCid) {
|
|
3072
|
+
disclosedContracts.push({ templateId: amuletCtx.featuredAppRight.templateId || null, contractId: amuletCtx.featuredAppRight.contractCid, createdEventBlob: amuletCtx.featuredAppRight.disclosureCid, synchronizerId: amuletCtx.featuredAppRight.synchronizerId });
|
|
3073
|
+
}
|
|
3074
|
+
} else {
|
|
3075
|
+
const instrumentDef = resolveInstrumentDefinition(assetId);
|
|
3076
|
+
const networkDef = instrumentDef?.[config.NETWORK];
|
|
3077
|
+
const registrar = networkDef?.registrar || getInstrumentRegistrar(assetId) || config.VALIDATOR_REGISTRAR_PARTY_ID;
|
|
3078
|
+
const [allocFactory, instConfig] = await Promise.all([
|
|
3079
|
+
resolveUtilityAllocationFactory(registrar, sender),
|
|
3080
|
+
resolveUtilityInstrumentConfiguration(assetId, registrar),
|
|
3081
|
+
]);
|
|
3082
|
+
if (instConfig) {
|
|
3083
|
+
choiceContextData = {
|
|
3084
|
+
values: {
|
|
3085
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfiguration]: { tag: "AV_ContractId", value: instConfig.contractCid },
|
|
3086
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfigurationPrefixed]: { tag: "AV_ContractId", value: instConfig.contractCid },
|
|
3087
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentials]: { tag: "AV_List", value: [] },
|
|
3088
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentialsPrefixed]: { tag: "AV_List", value: [] },
|
|
3089
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentials]: { tag: "AV_List", value: [] },
|
|
3090
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentialsPrefixed]: { tag: "AV_List", value: [] },
|
|
3091
|
+
[DEFAULT_AMULET_CONTEXT_KEYS.expireLock]: { tag: "AV_Bool", value: true },
|
|
3092
|
+
},
|
|
3093
|
+
};
|
|
3094
|
+
disclosedContracts.push({ templateId: null, contractId: instConfig.contractCid, createdEventBlob: instConfig.disclosureCid, synchronizerId: instConfig.synchronizerId });
|
|
3095
|
+
}
|
|
3096
|
+
if (allocFactory) {
|
|
3097
|
+
disclosedContracts.push({ templateId: null, contractId: allocFactory.contractCid, createdEventBlob: allocFactory.disclosureCid, synchronizerId: allocFactory.synchronizerId });
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
} else if (isAmulet) {
|
|
3101
|
+
// Resolve via disclosures API
|
|
3102
|
+
let resolvedData = disclosures?.disclosures || disclosures || null;
|
|
3103
|
+
if (!resolvedData?.choiceContext) {
|
|
3104
|
+
const disclosuresResult = await getDisclosures(sender);
|
|
3105
|
+
resolvedData = disclosuresResult?.disclosures;
|
|
3106
|
+
if (disclosuresResult?.error || !resolvedData?.choiceContext) {
|
|
3107
|
+
return { error: `buildAllocationWithdrawCommand: failed to resolve Amulet disclosures: ${disclosuresResult?.message || disclosuresResult?.error || "missing choiceContext"}` };
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
choiceContextData = resolvedData.choiceContext.choiceContextData || {};
|
|
3111
|
+
const values = choiceContextData.values;
|
|
3112
|
+
if (values && !values[DEFAULT_AMULET_CONTEXT_KEYS.expireLock]) {
|
|
3113
|
+
values[DEFAULT_AMULET_CONTEXT_KEYS.expireLock] = { tag: "AV_Bool", value: true };
|
|
3114
|
+
}
|
|
3115
|
+
disclosedContracts = (resolvedData.choiceContext.disclosedContracts || []).map(dc => ({
|
|
3116
|
+
templateId: dc.templateId, contractId: dc.contractId, createdEventBlob: dc.createdEventBlob, synchronizerId: dc.synchronizerId,
|
|
3117
|
+
}));
|
|
3118
|
+
} else {
|
|
3119
|
+
// Utility: build context from catalog data (no ledger/validator access needed)
|
|
3120
|
+
const instrumentDef = resolveInstrumentDefinition(assetId);
|
|
3121
|
+
const networkContracts = instrumentDef?.[config.NETWORK];
|
|
3122
|
+
if (!networkContracts) {
|
|
3123
|
+
return { error: `buildAllocationWithdrawCommand: no ${config.NETWORK} config for ${assetId} in instrument catalog` };
|
|
3124
|
+
}
|
|
3125
|
+
const instConfig = networkContracts.instrumentConfiguration;
|
|
3126
|
+
const allocFactory = networkContracts.allocationFactory;
|
|
3127
|
+
const synchronizerId = networkContracts.synchronizerId;
|
|
3128
|
+
|
|
3129
|
+
if (!instConfig?.contractId || !instConfig?.eventBlob) {
|
|
3130
|
+
return { error: `buildAllocationWithdrawCommand: instrumentConfiguration missing for ${assetId} on ${config.NETWORK}` };
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
choiceContextData = {
|
|
3134
|
+
values: {
|
|
3135
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfiguration]: { tag: "AV_ContractId", value: instConfig.contractId },
|
|
3136
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.instrumentConfigurationPrefixed]: { tag: "AV_ContractId", value: instConfig.contractId },
|
|
3137
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentials]: { tag: "AV_List", value: [] },
|
|
3138
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.senderCredentialsPrefixed]: { tag: "AV_List", value: [] },
|
|
3139
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentials]: { tag: "AV_List", value: [] },
|
|
3140
|
+
[DEFAULT_UTILITY_CONTEXT_KEYS.receiverCredentialsPrefixed]: { tag: "AV_List", value: [] },
|
|
3141
|
+
[DEFAULT_AMULET_CONTEXT_KEYS.expireLock]: { tag: "AV_Bool", value: true },
|
|
3142
|
+
},
|
|
3143
|
+
};
|
|
3144
|
+
disclosedContracts.push({
|
|
3145
|
+
templateId: instConfig.templateId || null,
|
|
3146
|
+
contractId: instConfig.contractId,
|
|
3147
|
+
createdEventBlob: instConfig.eventBlob,
|
|
3148
|
+
synchronizerId: synchronizerId,
|
|
3149
|
+
});
|
|
3150
|
+
if (allocFactory?.contractId && allocFactory?.eventBlob) {
|
|
3151
|
+
disclosedContracts.push({
|
|
3152
|
+
templateId: allocFactory.templateId || null,
|
|
3153
|
+
contractId: allocFactory.contractId,
|
|
3154
|
+
createdEventBlob: allocFactory.eventBlob,
|
|
3155
|
+
synchronizerId: synchronizerId,
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
// --- Build one command per allocation CID ---
|
|
3161
|
+
const endpoint = `${config.VALIDATOR_API_URL}/v2/commands/submit-and-wait`;
|
|
3162
|
+
const commands = cids.map(cid => {
|
|
3163
|
+
const allocationCid = normalizeContractId(cid);
|
|
3164
|
+
const command = {
|
|
3165
|
+
commands: [{
|
|
3166
|
+
ExerciseCommand: {
|
|
3167
|
+
templateId: "#splice-api-token-allocation-v1:Splice.Api.Token.AllocationV1:Allocation",
|
|
3168
|
+
contractId: allocationCid,
|
|
3169
|
+
choice: "Allocation_Withdraw",
|
|
3170
|
+
choiceArgument: {
|
|
3171
|
+
extraArgs: {
|
|
3172
|
+
context: choiceContextData,
|
|
3173
|
+
meta: { values: {} },
|
|
3174
|
+
},
|
|
3175
|
+
},
|
|
3176
|
+
},
|
|
3177
|
+
}],
|
|
3178
|
+
commandId: randomUUID(),
|
|
3179
|
+
userId: userId || getUserId() || config.AUTH0_USER_ID || "temple",
|
|
3180
|
+
applicationId: "temple",
|
|
3181
|
+
actAs: [sender],
|
|
3182
|
+
disclosedContracts: [...disclosedContracts],
|
|
3183
|
+
};
|
|
3184
|
+
dedupeDisclosedContracts(command);
|
|
3185
|
+
return { command, endpoint };
|
|
3186
|
+
});
|
|
3187
|
+
|
|
3188
|
+
return { commands };
|
|
3189
|
+
}
|
|
3190
|
+
|
|
2835
3191
|
/**
|
|
2836
3192
|
* Get all balances for a party, grouped by asset type.
|
|
2837
3193
|
* Returns Amulet, locked Amulet, and utility token holdings with totals.
|
|
@@ -4,3 +4,4 @@ export const instrumentIdToSymbol: Record<string, string>;
|
|
|
4
4
|
export const supportedTradingPairs: Array<{ baseAsset: string; quoteAsset: string }>;
|
|
5
5
|
export const supportedSymbols: Record<string, unknown>;
|
|
6
6
|
export function normalizeAssetId(symbol: string): string;
|
|
7
|
+
export function resolveOnChainInstrumentId(symbol: string): string;
|