@fairmint/canton-node-sdk 0.0.210 → 0.0.212
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/build/src/utils/amulet/external-party-transfer-offer.d.ts +73 -0
- package/build/src/utils/amulet/external-party-transfer-offer.d.ts.map +1 -0
- package/build/src/utils/amulet/external-party-transfer-offer.js +308 -0
- package/build/src/utils/amulet/external-party-transfer-offer.js.map +1 -0
- package/build/src/utils/amulet/index.d.ts +1 -0
- package/build/src/utils/amulet/index.d.ts.map +1 -1
- package/build/src/utils/amulet/index.js +1 -0
- package/build/src/utils/amulet/index.js.map +1 -1
- package/build/src/utils/canton-response-utils.d.ts +3 -0
- package/build/src/utils/canton-response-utils.d.ts.map +1 -0
- package/build/src/utils/canton-response-utils.js +18 -0
- package/build/src/utils/canton-response-utils.js.map +1 -0
- package/build/src/utils/external-signing/canton-protocol.d.ts +57 -0
- package/build/src/utils/external-signing/canton-protocol.d.ts.map +1 -0
- package/build/src/utils/external-signing/canton-protocol.js +339 -0
- package/build/src/utils/external-signing/canton-protocol.js.map +1 -0
- package/build/src/utils/external-signing/execute-external-transaction.d.ts +11 -0
- package/build/src/utils/external-signing/execute-external-transaction.d.ts.map +1 -1
- package/build/src/utils/external-signing/execute-external-transaction.js +24 -2
- package/build/src/utils/external-signing/execute-external-transaction.js.map +1 -1
- package/build/src/utils/external-signing/external-party-onboarding.d.ts +65 -0
- package/build/src/utils/external-signing/external-party-onboarding.d.ts.map +1 -0
- package/build/src/utils/external-signing/external-party-onboarding.js +249 -0
- package/build/src/utils/external-signing/external-party-onboarding.js.map +1 -0
- package/build/src/utils/external-signing/external-party-wallet.d.ts +243 -0
- package/build/src/utils/external-signing/external-party-wallet.d.ts.map +1 -0
- package/build/src/utils/external-signing/external-party-wallet.js +859 -0
- package/build/src/utils/external-signing/external-party-wallet.js.map +1 -0
- package/build/src/utils/external-signing/index.d.ts +3 -0
- package/build/src/utils/external-signing/index.d.ts.map +1 -1
- package/build/src/utils/external-signing/index.js +3 -0
- package/build/src/utils/external-signing/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_PROVIDER_TRANSFER_OFFER_TTL_MS = void 0;
|
|
4
|
+
exports.createExternalPartyWalletBridge = createExternalPartyWalletBridge;
|
|
5
|
+
exports.buildExternalPartyCcTransferPrepareTokenPayload = buildExternalPartyCcTransferPrepareTokenPayload;
|
|
6
|
+
exports.buildProviderTransferAcceptPrepareTokenPayload = buildProviderTransferAcceptPrepareTokenPayload;
|
|
7
|
+
exports.normalizeExternalPartyWalletDescription = normalizeExternalPartyWalletDescription;
|
|
8
|
+
exports.parseExternalPartyWalletConnectedSynchronizerId = parseExternalPartyWalletConnectedSynchronizerId;
|
|
9
|
+
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const errors_1 = require("../../core/errors");
|
|
11
|
+
const utils_1 = require("../../core/utils");
|
|
12
|
+
const external_party_cc_transfer_1 = require("../amulet/external-party-cc-transfer");
|
|
13
|
+
const external_party_transfer_offer_1 = require("../amulet/external-party-transfer-offer");
|
|
14
|
+
const canton_response_utils_1 = require("../canton-response-utils");
|
|
15
|
+
const canton_protocol_1 = require("./canton-protocol");
|
|
16
|
+
const external_party_onboarding_1 = require("./external-party-onboarding");
|
|
17
|
+
exports.DEFAULT_PROVIDER_TRANSFER_OFFER_TTL_MS = 24 * 60 * 60 * 1000;
|
|
18
|
+
function createExternalPartyWalletBridge(options) {
|
|
19
|
+
validateRequiredString('providerPartyId', options.providerPartyId);
|
|
20
|
+
validateRequiredString('providerUserId', options.providerUserId);
|
|
21
|
+
validateRequiredString('prepareTokenSecret', options.prepareTokenSecret);
|
|
22
|
+
const now = options.now ?? Date.now;
|
|
23
|
+
const randomId = options.randomId ?? node_crypto_1.randomUUID;
|
|
24
|
+
const providerTransferOfferTtlMs = validateProviderTransferOfferTtlMs(options.providerTransferOfferTtlMs ?? exports.DEFAULT_PROVIDER_TRANSFER_OFFER_TTL_MS);
|
|
25
|
+
return {
|
|
26
|
+
async getConnectedSynchronizers() {
|
|
27
|
+
return readConnectedSynchronizers(options.ledgerClient, options.providerPartyId);
|
|
28
|
+
},
|
|
29
|
+
async getProviderSourceBalance() {
|
|
30
|
+
const validatorClient = requireValidatorClient(options.validatorClient, 'provider source balance');
|
|
31
|
+
return {
|
|
32
|
+
sourcePartyId: options.providerPartyId,
|
|
33
|
+
fetchedAt: new Date(now()).toISOString(),
|
|
34
|
+
raw: await validatorClient.getWalletBalance(),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
async listExternalPartiesForPublicKey(input) {
|
|
38
|
+
if (input.partyName) {
|
|
39
|
+
const result = await (0, external_party_onboarding_1.getExternalPartyIdForHintAndPublicKey)(options.ledgerClient, input.partyName, input.publicKeyBase64);
|
|
40
|
+
return {
|
|
41
|
+
publicKeyFingerprint: result.publicKeyFingerprint,
|
|
42
|
+
parties: result.exists ? [result.partyId] : [],
|
|
43
|
+
raw: result.raw,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return (0, external_party_onboarding_1.listExternalPartyIdsForPublicKey)(options.ledgerClient, input.publicKeyBase64);
|
|
47
|
+
},
|
|
48
|
+
async prepareExternalParty(input) {
|
|
49
|
+
const synchronizerId = await readRequiredConnectedSynchronizerId(options.ledgerClient, options.providerPartyId);
|
|
50
|
+
const prepared = await (0, external_party_onboarding_1.prepareExternalPartyOnboarding)({
|
|
51
|
+
ledgerClient: options.ledgerClient,
|
|
52
|
+
synchronizerId,
|
|
53
|
+
partyHint: input.partyName,
|
|
54
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
partyId: prepared.partyId,
|
|
58
|
+
publicKeyFingerprint: prepared.publicKeyFingerprint,
|
|
59
|
+
multiHashHex: prepared.multiHashHex,
|
|
60
|
+
synchronizerId: prepared.synchronizerId,
|
|
61
|
+
topologyTransactions: [...prepared.topologyTransactions],
|
|
62
|
+
publicKeyFormat: prepared.publicKeyFormat,
|
|
63
|
+
signingAlgorithmSpec: prepared.signingAlgorithmSpec,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
async submitExternalPartySignature(input) {
|
|
67
|
+
(0, canton_protocol_1.assertCantonSha256MultihashHex)(input.multiHashHex);
|
|
68
|
+
const synchronizerId = await readRequiredConnectedSynchronizerId(options.ledgerClient, options.providerPartyId);
|
|
69
|
+
if (synchronizerId !== input.synchronizerId) {
|
|
70
|
+
throw new errors_1.ValidationError("Prepared Canton synchronizer no longer matches the provider's connected synchronizer", {
|
|
71
|
+
expectedSynchronizerId: input.synchronizerId,
|
|
72
|
+
synchronizerId,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const submitted = await (0, external_party_onboarding_1.submitExternalPartyOnboarding)({
|
|
76
|
+
ledgerClient: options.ledgerClient,
|
|
77
|
+
synchronizerId,
|
|
78
|
+
partyId: input.partyId,
|
|
79
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
80
|
+
multiHashHex: input.multiHashHex,
|
|
81
|
+
topologyTransactions: input.topologyTransactions,
|
|
82
|
+
multiHashSignatureBase64: input.multiHashSignatureBase64,
|
|
83
|
+
...(input.publicKeyFingerprint !== undefined ? { publicKeyFingerprint: input.publicKeyFingerprint } : {}),
|
|
84
|
+
allowAlreadyExists: true,
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
partyId: submitted.partyId,
|
|
88
|
+
raw: submitted.raw,
|
|
89
|
+
alreadyExisted: submitted.alreadyExisted,
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
async prepareCcTransfer(input) {
|
|
93
|
+
const validatorClient = requireValidatorClient(options.validatorClient, 'CC transfer prepare');
|
|
94
|
+
const amount = normalizeAmountString(input.amount);
|
|
95
|
+
const description = normalizeDescription(input.description);
|
|
96
|
+
const publicKeyFingerprint = (0, canton_protocol_1.assertCantonPartyMatchesPublicKey)({
|
|
97
|
+
partyId: input.senderPartyId,
|
|
98
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
99
|
+
});
|
|
100
|
+
const prepared = await (0, external_party_cc_transfer_1.prepareExternalPartyCcTransfer)(validatorClient, {
|
|
101
|
+
senderPartyId: input.senderPartyId,
|
|
102
|
+
receiverPartyId: input.receiverPartyId,
|
|
103
|
+
amount,
|
|
104
|
+
verboseHashing: false,
|
|
105
|
+
...(description ? { description } : {}),
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
senderPartyId: input.senderPartyId,
|
|
109
|
+
receiverPartyId: input.receiverPartyId,
|
|
110
|
+
amount,
|
|
111
|
+
description,
|
|
112
|
+
preparedTransaction: prepared.transaction,
|
|
113
|
+
preparedTransactionHashHex: prepared.transactionHashHex,
|
|
114
|
+
transferCommandContractIdPrefix: prepared.transferCommandContractIdPrefix,
|
|
115
|
+
nonce: prepared.nonce,
|
|
116
|
+
expiresAt: prepared.expiresAt,
|
|
117
|
+
prepareToken: (0, canton_protocol_1.buildCantonPrepareToken)(options.prepareTokenSecret, buildExternalPartyCcTransferPrepareTokenPayload({
|
|
118
|
+
provider: options.provider,
|
|
119
|
+
network: options.network,
|
|
120
|
+
tokenContext: input.tokenContext,
|
|
121
|
+
senderPartyId: input.senderPartyId,
|
|
122
|
+
receiverPartyId: input.receiverPartyId,
|
|
123
|
+
amount,
|
|
124
|
+
description,
|
|
125
|
+
publicKeyFingerprint,
|
|
126
|
+
preparedTransaction: prepared.transaction,
|
|
127
|
+
preparedTransactionHashHex: prepared.transactionHashHex,
|
|
128
|
+
transferCommandContractIdPrefix: prepared.transferCommandContractIdPrefix,
|
|
129
|
+
nonce: prepared.nonce,
|
|
130
|
+
expiresAt: prepared.expiresAt,
|
|
131
|
+
})),
|
|
132
|
+
hashingDetails: readOptionalString((0, canton_response_utils_1.objectOrEmpty)(prepared.raw), 'hashing_details'),
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
async submitCcTransfer(input, hooks = {}) {
|
|
136
|
+
const validatorClient = requireValidatorClient(options.validatorClient, 'CC transfer submit');
|
|
137
|
+
const amount = normalizeAmountString(input.amount);
|
|
138
|
+
const description = normalizeDescription(input.description);
|
|
139
|
+
const publicKeyFingerprint = (0, canton_protocol_1.assertCantonPartyMatchesPublicKey)({
|
|
140
|
+
partyId: input.senderPartyId,
|
|
141
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
142
|
+
...(input.publicKeyFingerprint !== undefined ? { publicKeyFingerprint: input.publicKeyFingerprint } : {}),
|
|
143
|
+
});
|
|
144
|
+
(0, canton_protocol_1.assertCantonPrepareToken)(options.prepareTokenSecret, input.prepareToken, buildExternalPartyCcTransferPrepareTokenPayload({
|
|
145
|
+
provider: options.provider,
|
|
146
|
+
network: options.network,
|
|
147
|
+
tokenContext: input.tokenContext,
|
|
148
|
+
senderPartyId: input.senderPartyId,
|
|
149
|
+
receiverPartyId: input.receiverPartyId,
|
|
150
|
+
amount,
|
|
151
|
+
description,
|
|
152
|
+
publicKeyFingerprint,
|
|
153
|
+
preparedTransaction: input.preparedTransaction,
|
|
154
|
+
preparedTransactionHashHex: input.preparedTransactionHashHex,
|
|
155
|
+
transferCommandContractIdPrefix: input.transferCommandContractIdPrefix,
|
|
156
|
+
nonce: input.nonce,
|
|
157
|
+
expiresAt: input.expiresAt,
|
|
158
|
+
}));
|
|
159
|
+
(0, canton_protocol_1.assertCantonHashSignature)({
|
|
160
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
161
|
+
hashHex: input.preparedTransactionHashHex,
|
|
162
|
+
signatureBase64: input.signatureBase64,
|
|
163
|
+
});
|
|
164
|
+
await hooks.beforeSubmit?.();
|
|
165
|
+
let submitted;
|
|
166
|
+
try {
|
|
167
|
+
submitted = await (0, external_party_cc_transfer_1.submitExternalPartyCcTransfer)(validatorClient, {
|
|
168
|
+
senderPartyId: input.senderPartyId,
|
|
169
|
+
transaction: input.preparedTransaction,
|
|
170
|
+
transactionHashSignatureHex: decodeBase64(input.signatureBase64, 'signatureBase64').toString('hex'),
|
|
171
|
+
publicKeyHex: (0, canton_protocol_1.extractRawEd25519PublicKey)(input.publicKeyBase64).toString('hex'),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
try {
|
|
176
|
+
await hooks.afterSubmitFailure?.(error);
|
|
177
|
+
}
|
|
178
|
+
catch (hookError) {
|
|
179
|
+
throw new errors_1.OperationError('afterSubmitFailure hook failed after Canton CC transfer submit failed', errors_1.OperationErrorCode.TRANSACTION_FAILED, {
|
|
180
|
+
senderPartyId: input.senderPartyId,
|
|
181
|
+
submitCause: errorToContext(error),
|
|
182
|
+
hookCause: errorToContext(hookError),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
const raw = (0, canton_response_utils_1.objectOrEmpty)(submitted.raw);
|
|
188
|
+
const updateId = readOptionalUpdateId(submitted);
|
|
189
|
+
if (!updateId) {
|
|
190
|
+
const missingUpdateIdError = new errors_1.OperationError('Canton CC transfer submit response did not include updateId', errors_1.OperationErrorCode.TRANSACTION_FAILED, {
|
|
191
|
+
senderPartyId: input.senderPartyId,
|
|
192
|
+
raw,
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
await hooks.afterSubmitWithoutUpdateId?.({
|
|
196
|
+
senderPartyId: input.senderPartyId,
|
|
197
|
+
raw,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch (hookError) {
|
|
201
|
+
throw new errors_1.OperationError('afterSubmitWithoutUpdateId hook failed after Canton CC transfer submit returned without updateId', errors_1.OperationErrorCode.TRANSACTION_FAILED, {
|
|
202
|
+
senderPartyId: input.senderPartyId,
|
|
203
|
+
raw,
|
|
204
|
+
submitCause: errorToContext(missingUpdateIdError),
|
|
205
|
+
hookCause: errorToContext(hookError),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
throw missingUpdateIdError;
|
|
209
|
+
}
|
|
210
|
+
const result = {
|
|
211
|
+
senderPartyId: input.senderPartyId,
|
|
212
|
+
updateId,
|
|
213
|
+
raw,
|
|
214
|
+
};
|
|
215
|
+
try {
|
|
216
|
+
await hooks.afterSubmit?.(result);
|
|
217
|
+
}
|
|
218
|
+
catch (hookError) {
|
|
219
|
+
throw new errors_1.OperationError('afterSubmit hook failed after Canton CC transfer submit succeeded', errors_1.OperationErrorCode.TRANSACTION_FAILED, {
|
|
220
|
+
senderPartyId: input.senderPartyId,
|
|
221
|
+
updateId,
|
|
222
|
+
raw,
|
|
223
|
+
hookCause: errorToContext(hookError),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
},
|
|
228
|
+
async prepareProviderTransfer(input) {
|
|
229
|
+
const validatorClient = requireValidatorClient(options.validatorClient, 'provider transfer prepare');
|
|
230
|
+
const amount = normalizeAmountString(input.amount);
|
|
231
|
+
const description = normalizeDescription(input.description);
|
|
232
|
+
const publicKeyFingerprint = (0, canton_protocol_1.assertCantonPartyMatchesPublicKey)({
|
|
233
|
+
partyId: input.receiverPartyId,
|
|
234
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
235
|
+
});
|
|
236
|
+
const sourceBalanceBefore = await validatorClient.getWalletBalance();
|
|
237
|
+
const preparedOffer = input.offerContractId
|
|
238
|
+
? await readResumableProviderTransferOffer({
|
|
239
|
+
validatorClient,
|
|
240
|
+
receiverPartyId: input.receiverPartyId,
|
|
241
|
+
amount,
|
|
242
|
+
description,
|
|
243
|
+
offerContractId: validateExistingOfferContractId(input.offerContractId),
|
|
244
|
+
offerUpdateId: input.offerUpdateId ?? null,
|
|
245
|
+
})
|
|
246
|
+
: await createProviderTransferOffer({
|
|
247
|
+
validatorClient,
|
|
248
|
+
receiverPartyId: input.receiverPartyId,
|
|
249
|
+
amount,
|
|
250
|
+
description,
|
|
251
|
+
expiresAt: epochMillisecondsToMicroseconds(now() + providerTransferOfferTtlMs),
|
|
252
|
+
trackingId: buildProviderTransferTrackingId({
|
|
253
|
+
receiverPartyId: input.receiverPartyId,
|
|
254
|
+
amount,
|
|
255
|
+
now: now(),
|
|
256
|
+
randomId: randomId(),
|
|
257
|
+
}),
|
|
258
|
+
});
|
|
259
|
+
const sourceBalanceAfter = await readOptionalProviderWalletBalance(validatorClient);
|
|
260
|
+
const commandId = input.commandId ?? `accept-provider-offer-${randomId()}`;
|
|
261
|
+
let preparedAccept;
|
|
262
|
+
try {
|
|
263
|
+
preparedAccept = await prepareProviderTransferAcceptance({
|
|
264
|
+
ledgerClient: options.ledgerClient,
|
|
265
|
+
validatorClient,
|
|
266
|
+
providerPartyId: options.providerPartyId,
|
|
267
|
+
providerUserId: options.providerUserId,
|
|
268
|
+
offerContractId: preparedOffer.offerContractId,
|
|
269
|
+
receiverPartyId: input.receiverPartyId,
|
|
270
|
+
commandId,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
throw new errors_1.OperationError('Canton provider-funded transfer acceptance prepare failed; retry with the returned offerContractId to avoid creating a duplicate offer', errors_1.OperationErrorCode.TRANSACTION_FAILED, {
|
|
275
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
276
|
+
sourcePartyId: options.providerPartyId,
|
|
277
|
+
receiverPartyId: input.receiverPartyId,
|
|
278
|
+
amount,
|
|
279
|
+
description,
|
|
280
|
+
offerContractId: preparedOffer.offerContractId,
|
|
281
|
+
offerUpdateId: preparedOffer.offerUpdateId,
|
|
282
|
+
trackingId: preparedOffer.trackingId,
|
|
283
|
+
commandId,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
sourcePartyId: options.providerPartyId,
|
|
288
|
+
receiverPartyId: input.receiverPartyId,
|
|
289
|
+
amount,
|
|
290
|
+
description,
|
|
291
|
+
offerContractId: preparedOffer.offerContractId,
|
|
292
|
+
offerUpdateId: preparedOffer.offerUpdateId,
|
|
293
|
+
commandId,
|
|
294
|
+
synchronizerId: preparedAccept.synchronizerId,
|
|
295
|
+
preparedTransaction: preparedAccept.preparedTransaction,
|
|
296
|
+
preparedTransactionHashHex: preparedAccept.preparedTransactionHashHex,
|
|
297
|
+
hashingSchemeVersion: preparedAccept.hashingSchemeVersion,
|
|
298
|
+
prepareToken: (0, canton_protocol_1.buildCantonPrepareToken)(options.prepareTokenSecret, buildProviderTransferAcceptPrepareTokenPayload({
|
|
299
|
+
provider: options.provider,
|
|
300
|
+
network: options.network,
|
|
301
|
+
tokenContext: input.tokenContext,
|
|
302
|
+
sourcePartyId: options.providerPartyId,
|
|
303
|
+
receiverPartyId: input.receiverPartyId,
|
|
304
|
+
amount,
|
|
305
|
+
description,
|
|
306
|
+
publicKeyFingerprint,
|
|
307
|
+
offerContractId: preparedOffer.offerContractId,
|
|
308
|
+
offerUpdateId: preparedOffer.offerUpdateId,
|
|
309
|
+
commandId,
|
|
310
|
+
synchronizerId: preparedAccept.synchronizerId,
|
|
311
|
+
preparedTransaction: preparedAccept.preparedTransaction,
|
|
312
|
+
preparedTransactionHashHex: preparedAccept.preparedTransactionHashHex,
|
|
313
|
+
hashingSchemeVersion: preparedAccept.hashingSchemeVersion,
|
|
314
|
+
})),
|
|
315
|
+
sourceBalanceBefore,
|
|
316
|
+
sourceBalanceAfter,
|
|
317
|
+
raw: {
|
|
318
|
+
offer: preparedOffer.offer,
|
|
319
|
+
offerStatus: preparedOffer.offerStatus,
|
|
320
|
+
offerDisclosure: preparedAccept.offerDisclosure.raw,
|
|
321
|
+
prepared: preparedAccept.raw,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
},
|
|
325
|
+
async submitProviderTransfer(input) {
|
|
326
|
+
const validatorClient = requireValidatorClient(options.validatorClient, 'provider transfer submit');
|
|
327
|
+
const amount = normalizeAmountString(input.amount);
|
|
328
|
+
const description = normalizeDescription(input.description);
|
|
329
|
+
const publicKeyFingerprint = (0, canton_protocol_1.assertCantonPartyMatchesPublicKey)({
|
|
330
|
+
partyId: input.receiverPartyId,
|
|
331
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
332
|
+
...(input.publicKeyFingerprint !== undefined ? { publicKeyFingerprint: input.publicKeyFingerprint } : {}),
|
|
333
|
+
});
|
|
334
|
+
const hashingSchemeVersion = input.hashingSchemeVersion ?? 'HASHING_SCHEME_VERSION_V2';
|
|
335
|
+
(0, canton_protocol_1.assertCantonPrepareToken)(options.prepareTokenSecret, input.prepareToken, buildProviderTransferAcceptPrepareTokenPayload({
|
|
336
|
+
provider: options.provider,
|
|
337
|
+
network: options.network,
|
|
338
|
+
tokenContext: input.tokenContext,
|
|
339
|
+
sourcePartyId: options.providerPartyId,
|
|
340
|
+
receiverPartyId: input.receiverPartyId,
|
|
341
|
+
amount,
|
|
342
|
+
description,
|
|
343
|
+
publicKeyFingerprint,
|
|
344
|
+
offerContractId: input.offerContractId,
|
|
345
|
+
offerUpdateId: input.offerUpdateId,
|
|
346
|
+
commandId: input.commandId,
|
|
347
|
+
synchronizerId: input.synchronizerId,
|
|
348
|
+
preparedTransaction: input.preparedTransaction,
|
|
349
|
+
preparedTransactionHashHex: input.preparedTransactionHashHex,
|
|
350
|
+
hashingSchemeVersion,
|
|
351
|
+
}));
|
|
352
|
+
(0, canton_protocol_1.assertCantonHashSignature)({
|
|
353
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
354
|
+
hashHex: input.preparedTransactionHashHex,
|
|
355
|
+
signatureBase64: input.signatureBase64,
|
|
356
|
+
});
|
|
357
|
+
const submitted = await (0, external_party_transfer_offer_1.submitExternalPartyTransferOfferAcceptance)({
|
|
358
|
+
ledgerClient: options.ledgerClient,
|
|
359
|
+
userId: options.providerUserId,
|
|
360
|
+
acceptingPartyId: input.receiverPartyId,
|
|
361
|
+
publicKeyBase64: input.publicKeyBase64,
|
|
362
|
+
publicKeyFingerprint,
|
|
363
|
+
preparedTransaction: input.preparedTransaction,
|
|
364
|
+
preparedTransactionHashHex: input.preparedTransactionHashHex,
|
|
365
|
+
signatureBase64: input.signatureBase64,
|
|
366
|
+
hashingSchemeVersion,
|
|
367
|
+
submissionId: randomId(),
|
|
368
|
+
});
|
|
369
|
+
const acceptUpdateId = readRequiredUpdateId(submitted, 'provider CC transfer accept submit');
|
|
370
|
+
const sourceBalanceAfter = await readOptionalProviderWalletBalance(validatorClient);
|
|
371
|
+
return {
|
|
372
|
+
sourcePartyId: options.providerPartyId,
|
|
373
|
+
receiverPartyId: input.receiverPartyId,
|
|
374
|
+
amount,
|
|
375
|
+
description,
|
|
376
|
+
offerContractId: input.offerContractId,
|
|
377
|
+
offerUpdateId: input.offerUpdateId,
|
|
378
|
+
acceptUpdateId,
|
|
379
|
+
updateId: acceptUpdateId,
|
|
380
|
+
sourceBalanceAfter,
|
|
381
|
+
raw: (0, canton_response_utils_1.objectOrEmpty)(submitted.raw),
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
async listActiveContracts(input) {
|
|
385
|
+
const contracts = await options.ledgerClient.getActiveContracts({
|
|
386
|
+
parties: [input.partyId],
|
|
387
|
+
...(input.templateIds && input.templateIds.length > 0 ? { templateIds: [...input.templateIds] } : {}),
|
|
388
|
+
includeCreatedEventBlob: input.includeCreatedEventBlob ?? true,
|
|
389
|
+
});
|
|
390
|
+
if (!Array.isArray(contracts)) {
|
|
391
|
+
throw new errors_1.OperationError('Canton active contracts response did not include an array', errors_1.OperationErrorCode.TRANSACTION_FAILED);
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
contracts: contracts.slice(0, input.limit ?? 50),
|
|
395
|
+
};
|
|
396
|
+
},
|
|
397
|
+
async getExternalPartyBalance(input) {
|
|
398
|
+
const validatorClient = requireValidatorClient(options.validatorClient, 'external party balance');
|
|
399
|
+
let raw;
|
|
400
|
+
try {
|
|
401
|
+
raw = await validatorClient.getExternalPartyBalance({ partyId: input.partyId });
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
if (!isNotFound(error))
|
|
405
|
+
throw error;
|
|
406
|
+
raw = await readExternalPartyBalanceFromActiveAmulets(options.ledgerClient, input.partyId);
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
partyId: input.partyId,
|
|
410
|
+
fetchedAt: new Date(now()).toISOString(),
|
|
411
|
+
raw,
|
|
412
|
+
};
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function buildExternalPartyCcTransferPrepareTokenPayload(input) {
|
|
417
|
+
return {
|
|
418
|
+
kind: 'external-party-cc-transfer',
|
|
419
|
+
version: 1,
|
|
420
|
+
provider: input.provider ?? null,
|
|
421
|
+
network: input.network ?? null,
|
|
422
|
+
context: normalizeTokenContext(input.tokenContext),
|
|
423
|
+
senderPartyId: input.senderPartyId,
|
|
424
|
+
receiverPartyId: input.receiverPartyId,
|
|
425
|
+
amount: input.amount,
|
|
426
|
+
description: input.description,
|
|
427
|
+
publicKeyFingerprint: input.publicKeyFingerprint,
|
|
428
|
+
preparedTransactionSha256: (0, canton_protocol_1.hashPreparedTransaction)(input.preparedTransaction),
|
|
429
|
+
preparedTransactionHashHex: input.preparedTransactionHashHex,
|
|
430
|
+
transferCommandContractIdPrefix: input.transferCommandContractIdPrefix,
|
|
431
|
+
nonce: input.nonce,
|
|
432
|
+
expiresAt: input.expiresAt,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function buildProviderTransferAcceptPrepareTokenPayload(input) {
|
|
436
|
+
return {
|
|
437
|
+
kind: 'provider-transfer-accept',
|
|
438
|
+
version: 1,
|
|
439
|
+
provider: input.provider ?? null,
|
|
440
|
+
network: input.network ?? null,
|
|
441
|
+
context: normalizeTokenContext(input.tokenContext),
|
|
442
|
+
sourcePartyId: input.sourcePartyId,
|
|
443
|
+
receiverPartyId: input.receiverPartyId,
|
|
444
|
+
amount: input.amount,
|
|
445
|
+
description: input.description,
|
|
446
|
+
publicKeyFingerprint: input.publicKeyFingerprint,
|
|
447
|
+
offerContractId: input.offerContractId,
|
|
448
|
+
offerUpdateId: input.offerUpdateId,
|
|
449
|
+
commandId: input.commandId,
|
|
450
|
+
synchronizerId: input.synchronizerId,
|
|
451
|
+
preparedTransactionSha256: (0, canton_protocol_1.hashPreparedTransaction)(input.preparedTransaction),
|
|
452
|
+
preparedTransactionHashHex: input.preparedTransactionHashHex,
|
|
453
|
+
hashingSchemeVersion: input.hashingSchemeVersion,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function normalizeExternalPartyWalletDescription(description) {
|
|
457
|
+
const normalized = description?.trim();
|
|
458
|
+
if (!normalized)
|
|
459
|
+
return null;
|
|
460
|
+
return normalized;
|
|
461
|
+
}
|
|
462
|
+
function parseExternalPartyWalletConnectedSynchronizerId(raw) {
|
|
463
|
+
if (!(0, utils_1.isRecord)(raw) && !Array.isArray(raw))
|
|
464
|
+
return null;
|
|
465
|
+
const direct = (0, utils_1.isRecord)(raw) ? raw['connectedSynchronizers'] : undefined;
|
|
466
|
+
const itemsField = (0, utils_1.isRecord)(raw) ? raw['items'] : undefined;
|
|
467
|
+
const items = Array.isArray(raw)
|
|
468
|
+
? raw
|
|
469
|
+
: Array.isArray(direct)
|
|
470
|
+
? direct
|
|
471
|
+
: Array.isArray(itemsField)
|
|
472
|
+
? itemsField
|
|
473
|
+
: [];
|
|
474
|
+
const synchronizerIds = [];
|
|
475
|
+
for (const item of items) {
|
|
476
|
+
if (typeof item === 'string' && item.trim()) {
|
|
477
|
+
synchronizerIds.push(item.trim());
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (!(0, utils_1.isRecord)(item))
|
|
481
|
+
continue;
|
|
482
|
+
for (const key of ['synchronizerId', 'synchronizer', 'domainId', 'id']) {
|
|
483
|
+
const value = item[key];
|
|
484
|
+
if (typeof value === 'string' && value.trim()) {
|
|
485
|
+
synchronizerIds.push(value.trim());
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const uniqueSynchronizerIds = new Set(synchronizerIds);
|
|
491
|
+
if (uniqueSynchronizerIds.size > 1) {
|
|
492
|
+
throw new errors_1.OperationError('Canton provider reported multiple connected synchronizers', errors_1.OperationErrorCode.MISSING_DOMAIN_ID, { synchronizerIds });
|
|
493
|
+
}
|
|
494
|
+
return synchronizerIds[0] ?? null;
|
|
495
|
+
}
|
|
496
|
+
async function prepareProviderTransferAcceptance(input) {
|
|
497
|
+
return (0, external_party_transfer_offer_1.prepareExternalPartyTransferOfferAcceptance)({
|
|
498
|
+
ledgerClient: input.ledgerClient,
|
|
499
|
+
validatorClient: input.validatorClient,
|
|
500
|
+
providerPartyId: input.providerPartyId,
|
|
501
|
+
commandId: input.commandId,
|
|
502
|
+
userId: input.providerUserId,
|
|
503
|
+
acceptingPartyId: input.receiverPartyId,
|
|
504
|
+
offerContractId: input.offerContractId,
|
|
505
|
+
...(input.synchronizerId ? { synchronizerId: input.synchronizerId } : {}),
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
async function createProviderTransferOffer(input) {
|
|
509
|
+
const offer = await input.validatorClient.createTransferOffer({
|
|
510
|
+
receiver_party_id: input.receiverPartyId,
|
|
511
|
+
amount: input.amount,
|
|
512
|
+
description: input.description ?? '',
|
|
513
|
+
expires_at: input.expiresAt,
|
|
514
|
+
tracking_id: input.trackingId,
|
|
515
|
+
});
|
|
516
|
+
const offerContractId = (0, canton_response_utils_1.readRequiredString)(offer, 'offer_contract_id', 'provider CC transfer offer');
|
|
517
|
+
const offerStatus = await readOptionalTransferOfferStatus(input.validatorClient, input.trackingId);
|
|
518
|
+
return {
|
|
519
|
+
offer,
|
|
520
|
+
offerContractId,
|
|
521
|
+
offerStatus,
|
|
522
|
+
offerUpdateId: readOptionalUpdateId(offerStatus),
|
|
523
|
+
trackingId: input.trackingId,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
async function readResumableProviderTransferOffer(input) {
|
|
527
|
+
const offerList = await input.validatorClient.listTransferOffers();
|
|
528
|
+
const offer = readTransferOfferByContractId(offerList, input.offerContractId);
|
|
529
|
+
if (!offer) {
|
|
530
|
+
throw new errors_1.OperationError('Canton transfer offer was not found for provider transfer resume', errors_1.OperationErrorCode.MISSING_CONTRACT, { offerContractId: input.offerContractId });
|
|
531
|
+
}
|
|
532
|
+
assertResumedProviderTransferOfferMatches({
|
|
533
|
+
offer,
|
|
534
|
+
offerContractId: input.offerContractId,
|
|
535
|
+
receiverPartyId: input.receiverPartyId,
|
|
536
|
+
amount: input.amount,
|
|
537
|
+
description: input.description,
|
|
538
|
+
});
|
|
539
|
+
return {
|
|
540
|
+
offer,
|
|
541
|
+
offerContractId: input.offerContractId,
|
|
542
|
+
offerStatus: null,
|
|
543
|
+
offerUpdateId: input.offerUpdateId,
|
|
544
|
+
trackingId: readTransferOfferString(offer, ['trackingId', 'tracking_id'], false),
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function validateExistingOfferContractId(offerContractId) {
|
|
548
|
+
const normalized = offerContractId.trim();
|
|
549
|
+
if (!normalized) {
|
|
550
|
+
throw new errors_1.ValidationError('offerContractId is required when resuming provider transfer prepare');
|
|
551
|
+
}
|
|
552
|
+
return normalized;
|
|
553
|
+
}
|
|
554
|
+
async function readConnectedSynchronizers(ledgerClient, providerPartyId) {
|
|
555
|
+
const raw = await ledgerClient.getConnectedSynchronizers({ party: providerPartyId });
|
|
556
|
+
return {
|
|
557
|
+
synchronizerId: parseExternalPartyWalletConnectedSynchronizerId(raw),
|
|
558
|
+
raw,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
async function readRequiredConnectedSynchronizerId(ledgerClient, providerPartyId) {
|
|
562
|
+
const { synchronizerId } = await readConnectedSynchronizers(ledgerClient, providerPartyId);
|
|
563
|
+
if (synchronizerId)
|
|
564
|
+
return synchronizerId;
|
|
565
|
+
throw new errors_1.OperationError('Canton provider did not report a connected synchronizer', errors_1.OperationErrorCode.MISSING_DOMAIN_ID);
|
|
566
|
+
}
|
|
567
|
+
async function readExternalPartyBalanceFromActiveAmulets(ledgerClient, partyId) {
|
|
568
|
+
const contracts = await ledgerClient.getActiveContracts({
|
|
569
|
+
parties: [partyId],
|
|
570
|
+
includeCreatedEventBlob: false,
|
|
571
|
+
});
|
|
572
|
+
const amulets = readAmuletBalanceContracts(contracts, partyId);
|
|
573
|
+
return {
|
|
574
|
+
source: 'ledger-active-contracts',
|
|
575
|
+
reason: 'validator-external-party-wallet-not-found',
|
|
576
|
+
effective_unlocked_qty: sumDecimalStrings(amulets.map((amulet) => amulet.amount)),
|
|
577
|
+
effective_locked_qty: '0',
|
|
578
|
+
contracts: amulets,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function readAmuletBalanceContracts(source, ownerPartyId) {
|
|
582
|
+
if (!Array.isArray(source))
|
|
583
|
+
return [];
|
|
584
|
+
const amulets = [];
|
|
585
|
+
for (const item of source) {
|
|
586
|
+
const contractEntry = (0, utils_1.isRecord)(item) ? (0, canton_response_utils_1.objectOrEmpty)(item['contractEntry']) : {};
|
|
587
|
+
const activeContract = (0, canton_response_utils_1.objectOrEmpty)(contractEntry['JsActiveContract']);
|
|
588
|
+
const createdEvent = (0, canton_response_utils_1.objectOrEmpty)(activeContract['createdEvent']);
|
|
589
|
+
const templateId = readFirstString(createdEvent, ['templateId', 'template_id']);
|
|
590
|
+
if (!templateId?.includes(':Splice.Amulet:Amulet'))
|
|
591
|
+
continue;
|
|
592
|
+
const createArgument = (0, canton_response_utils_1.objectOrEmpty)(createdEvent['createArgument']);
|
|
593
|
+
if (createArgument['owner'] !== ownerPartyId)
|
|
594
|
+
continue;
|
|
595
|
+
const amount = (0, canton_response_utils_1.objectOrEmpty)(createArgument['amount']);
|
|
596
|
+
const initialAmount = readFirstString(amount, ['initialAmount', 'initial_amount']);
|
|
597
|
+
if (!initialAmount)
|
|
598
|
+
continue;
|
|
599
|
+
amulets.push({
|
|
600
|
+
contractId: readFirstString(createdEvent, ['contractId', 'contract_id']),
|
|
601
|
+
templateId,
|
|
602
|
+
amount: initialAmount,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return amulets;
|
|
606
|
+
}
|
|
607
|
+
function buildProviderTransferTrackingId(input) {
|
|
608
|
+
return `provider-funding-${(0, node_crypto_1.createHash)('sha256')
|
|
609
|
+
.update(`${input.receiverPartyId}:${input.amount}:${input.now}:${input.randomId}`)
|
|
610
|
+
.digest('hex')
|
|
611
|
+
.slice(0, 32)}`;
|
|
612
|
+
}
|
|
613
|
+
function epochMillisecondsToMicroseconds(epochMilliseconds) {
|
|
614
|
+
return epochMilliseconds * 1000;
|
|
615
|
+
}
|
|
616
|
+
function validateProviderTransferOfferTtlMs(providerTransferOfferTtlMs) {
|
|
617
|
+
if (!Number.isSafeInteger(providerTransferOfferTtlMs) || providerTransferOfferTtlMs <= 0) {
|
|
618
|
+
throw new errors_1.ValidationError('providerTransferOfferTtlMs must be a positive safe integer', {
|
|
619
|
+
providerTransferOfferTtlMs,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return providerTransferOfferTtlMs;
|
|
623
|
+
}
|
|
624
|
+
function requireValidatorClient(validatorClient, operation) {
|
|
625
|
+
if (!validatorClient) {
|
|
626
|
+
throw new errors_1.ConfigurationError(`validatorClient is required for ${operation}`);
|
|
627
|
+
}
|
|
628
|
+
return validatorClient;
|
|
629
|
+
}
|
|
630
|
+
async function readOptionalTransferOfferStatus(validatorClient, trackingId) {
|
|
631
|
+
try {
|
|
632
|
+
return await validatorClient.getTransferOfferStatus({ trackingId });
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
if (!isNotFound(error))
|
|
636
|
+
throw error;
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async function readOptionalProviderWalletBalance(validatorClient) {
|
|
641
|
+
try {
|
|
642
|
+
return await validatorClient.getWalletBalance();
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
if (!isNotFound(error))
|
|
646
|
+
throw error;
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function readRequiredUpdateId(source, operation) {
|
|
651
|
+
const updateId = readOptionalUpdateId(source);
|
|
652
|
+
if (updateId)
|
|
653
|
+
return updateId;
|
|
654
|
+
throw new errors_1.OperationError(`Canton ${operation} response did not include updateId`, errors_1.OperationErrorCode.TRANSACTION_FAILED);
|
|
655
|
+
}
|
|
656
|
+
function readOptionalUpdateId(source) {
|
|
657
|
+
if (!(0, utils_1.isRecord)(source))
|
|
658
|
+
return null;
|
|
659
|
+
for (const key of ['updateId', 'update_id', 'transaction_id', 'transactionId']) {
|
|
660
|
+
const value = source[key];
|
|
661
|
+
if (typeof value === 'string' && value.trim())
|
|
662
|
+
return value;
|
|
663
|
+
}
|
|
664
|
+
return readOptionalUpdateId(source['raw']);
|
|
665
|
+
}
|
|
666
|
+
function readOptionalString(source, key) {
|
|
667
|
+
const value = source[key];
|
|
668
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
669
|
+
}
|
|
670
|
+
function readFirstString(record, keys) {
|
|
671
|
+
for (const key of keys) {
|
|
672
|
+
const value = record[key];
|
|
673
|
+
if (typeof value === 'string' && value.trim())
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
const CANTON_DECIMAL_AMOUNT_PATTERN = /^(?:0|[1-9]\d*)(?:\.\d+)?$/;
|
|
679
|
+
function normalizeAmountString(amount) {
|
|
680
|
+
const normalized = typeof amount === 'number' ? amount.toString() : amount.trim();
|
|
681
|
+
if (typeof amount === 'number') {
|
|
682
|
+
if (!Number.isFinite(amount)) {
|
|
683
|
+
throw new errors_1.ValidationError('amount must be a finite decimal amount', { amount });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (!normalized) {
|
|
687
|
+
throw new errors_1.ValidationError('amount is required', { amount });
|
|
688
|
+
}
|
|
689
|
+
if (!CANTON_DECIMAL_AMOUNT_PATTERN.test(normalized) || decimalAmountIsZero(normalized)) {
|
|
690
|
+
throw new errors_1.ValidationError('amount must be a positive decimal amount', { amount });
|
|
691
|
+
}
|
|
692
|
+
return normalized;
|
|
693
|
+
}
|
|
694
|
+
function decimalAmountIsZero(amount) {
|
|
695
|
+
return /^0(?:\.0+)?$/.test(amount);
|
|
696
|
+
}
|
|
697
|
+
function readTransferOfferByContractId(source, offerContractId) {
|
|
698
|
+
const sourceOffers = (0, utils_1.isRecord)(source) ? source['offers'] : undefined;
|
|
699
|
+
const offers = Array.isArray(sourceOffers) ? sourceOffers : source;
|
|
700
|
+
if (!Array.isArray(offers))
|
|
701
|
+
return null;
|
|
702
|
+
for (const offer of offers) {
|
|
703
|
+
if (!(0, utils_1.isRecord)(offer))
|
|
704
|
+
continue;
|
|
705
|
+
const contractId = readTransferOfferString(offer, ['contractId', 'contract_id'], false);
|
|
706
|
+
if (contractId === offerContractId)
|
|
707
|
+
return offer;
|
|
708
|
+
}
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
function assertResumedProviderTransferOfferMatches(input) {
|
|
712
|
+
const receiverPartyId = readTransferOfferString(input.offer, ['receiverPartyId', 'receiver_party_id', 'receiver', 'receiverParty'], false);
|
|
713
|
+
const amount = readTransferOfferString(input.offer, ['amount'], false);
|
|
714
|
+
const description = readTransferOfferString(input.offer, ['description'], true);
|
|
715
|
+
if (receiverPartyId === null || amount === null || description === null) {
|
|
716
|
+
const missingFields = [];
|
|
717
|
+
if (receiverPartyId === null)
|
|
718
|
+
missingFields.push('receiverPartyId');
|
|
719
|
+
if (amount === null)
|
|
720
|
+
missingFields.push('amount');
|
|
721
|
+
if (description === null)
|
|
722
|
+
missingFields.push('description');
|
|
723
|
+
throw new errors_1.OperationError('Canton transfer-offer details were unavailable for provider transfer resume', errors_1.OperationErrorCode.MISSING_CONTRACT, {
|
|
724
|
+
offerContractId: input.offerContractId,
|
|
725
|
+
missingFields,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
const expectedDescription = input.description ?? '';
|
|
729
|
+
const amountMatches = normalizeDecimalForComparison(amount) !== null &&
|
|
730
|
+
normalizeDecimalForComparison(amount) === normalizeDecimalForComparison(input.amount);
|
|
731
|
+
if (receiverPartyId !== input.receiverPartyId || !amountMatches || description !== expectedDescription) {
|
|
732
|
+
throw new errors_1.ValidationError('Resumed provider transfer offer does not match the requested transfer details', {
|
|
733
|
+
offerContractId: input.offerContractId,
|
|
734
|
+
expected: {
|
|
735
|
+
receiverPartyId: input.receiverPartyId,
|
|
736
|
+
amount: input.amount,
|
|
737
|
+
description: expectedDescription,
|
|
738
|
+
},
|
|
739
|
+
actual: {
|
|
740
|
+
receiverPartyId,
|
|
741
|
+
amount,
|
|
742
|
+
description,
|
|
743
|
+
},
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function readTransferOfferString(source, keys, allowEmpty) {
|
|
748
|
+
for (const record of readTransferOfferCandidateRecords(source)) {
|
|
749
|
+
for (const key of keys) {
|
|
750
|
+
const value = record[key];
|
|
751
|
+
if (typeof value === 'string') {
|
|
752
|
+
const normalized = value.trim();
|
|
753
|
+
if (normalized || allowEmpty)
|
|
754
|
+
return normalized;
|
|
755
|
+
}
|
|
756
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
757
|
+
return value.toString();
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
function readTransferOfferCandidateRecords(source) {
|
|
764
|
+
const transferOffer = (0, canton_response_utils_1.objectOrEmpty)(source['transfer_offer']);
|
|
765
|
+
const transferOfferContract = (0, canton_response_utils_1.objectOrEmpty)(transferOffer['contract']);
|
|
766
|
+
const transferOfferPayload = (0, canton_response_utils_1.objectOrEmpty)(transferOffer['payload']);
|
|
767
|
+
const offer = (0, canton_response_utils_1.objectOrEmpty)(source['offer']);
|
|
768
|
+
const offerContract = (0, canton_response_utils_1.objectOrEmpty)(offer['contract']);
|
|
769
|
+
const offerPayload = (0, canton_response_utils_1.objectOrEmpty)(offer['payload']);
|
|
770
|
+
const contract = (0, canton_response_utils_1.objectOrEmpty)(source['contract']);
|
|
771
|
+
const contractPayload = (0, canton_response_utils_1.objectOrEmpty)(contract['payload']);
|
|
772
|
+
const payload = (0, canton_response_utils_1.objectOrEmpty)(source['payload']);
|
|
773
|
+
const createArgument = (0, canton_response_utils_1.objectOrEmpty)(source['createArgument'] ?? source['create_argument']);
|
|
774
|
+
return [
|
|
775
|
+
source,
|
|
776
|
+
payload,
|
|
777
|
+
createArgument,
|
|
778
|
+
contract,
|
|
779
|
+
contractPayload,
|
|
780
|
+
offer,
|
|
781
|
+
offerPayload,
|
|
782
|
+
offerContract,
|
|
783
|
+
transferOffer,
|
|
784
|
+
transferOfferPayload,
|
|
785
|
+
transferOfferContract,
|
|
786
|
+
];
|
|
787
|
+
}
|
|
788
|
+
function normalizeDecimalForComparison(amount) {
|
|
789
|
+
const normalized = amount.trim();
|
|
790
|
+
if (!/^\d+(?:\.\d+)?$/.test(normalized))
|
|
791
|
+
return null;
|
|
792
|
+
const [whole = '0', fraction = ''] = normalized.split('.');
|
|
793
|
+
const normalizedWhole = whole.replace(/^0+(?=\d)/, '') || '0';
|
|
794
|
+
const normalizedFraction = fraction.replace(/0+$/, '');
|
|
795
|
+
return normalizedFraction ? `${normalizedWhole}.${normalizedFraction}` : normalizedWhole;
|
|
796
|
+
}
|
|
797
|
+
function normalizeDescription(description) {
|
|
798
|
+
return normalizeExternalPartyWalletDescription(description);
|
|
799
|
+
}
|
|
800
|
+
function normalizeTokenContext(context) {
|
|
801
|
+
if (!context)
|
|
802
|
+
return {};
|
|
803
|
+
return Object.fromEntries(Object.entries(context).filter(([, value]) => value !== undefined));
|
|
804
|
+
}
|
|
805
|
+
function decodeBase64(value, label) {
|
|
806
|
+
const normalized = value.trim().replace(/-/g, '+').replace(/_/g, '/');
|
|
807
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(normalized) || normalized.length % 4 === 1) {
|
|
808
|
+
throw new errors_1.ValidationError(`${label} must be base64-encoded`);
|
|
809
|
+
}
|
|
810
|
+
return Buffer.from(normalized, 'base64');
|
|
811
|
+
}
|
|
812
|
+
function errorToContext(error) {
|
|
813
|
+
const record = (0, utils_1.isRecord)(error) ? error : {};
|
|
814
|
+
const message = typeof record['message'] === 'string' && record['message'].trim()
|
|
815
|
+
? record['message']
|
|
816
|
+
: error instanceof Error
|
|
817
|
+
? error.message
|
|
818
|
+
: String(error);
|
|
819
|
+
return {
|
|
820
|
+
...(typeof record['name'] === 'string' ? { name: record['name'] } : {}),
|
|
821
|
+
message,
|
|
822
|
+
...(record['code'] !== undefined ? { code: record['code'] } : {}),
|
|
823
|
+
...(record['status'] !== undefined ? { status: record['status'] } : {}),
|
|
824
|
+
...(record['context'] !== undefined ? { context: record['context'] } : {}),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function isNotFound(error) {
|
|
828
|
+
if (error instanceof errors_1.ApiError) {
|
|
829
|
+
return error.status === 404;
|
|
830
|
+
}
|
|
831
|
+
return (0, utils_1.isRecord)(error) && error['status'] === 404;
|
|
832
|
+
}
|
|
833
|
+
function sumDecimalStrings(values) {
|
|
834
|
+
let scale = 0;
|
|
835
|
+
const parsed = values.map((value) => {
|
|
836
|
+
const [whole = '0', fraction = ''] = value.split('.');
|
|
837
|
+
scale = Math.max(scale, fraction.length);
|
|
838
|
+
return { whole, fraction };
|
|
839
|
+
});
|
|
840
|
+
let total = 0n;
|
|
841
|
+
for (const value of parsed) {
|
|
842
|
+
const whole = BigInt(value.whole === '' ? '0' : value.whole);
|
|
843
|
+
const paddedFraction = value.fraction.padEnd(scale, '0');
|
|
844
|
+
const fraction = BigInt(paddedFraction === '' ? '0' : paddedFraction);
|
|
845
|
+
total += whole * 10n ** BigInt(scale) + fraction;
|
|
846
|
+
}
|
|
847
|
+
if (scale === 0)
|
|
848
|
+
return total.toString();
|
|
849
|
+
const divisor = 10n ** BigInt(scale);
|
|
850
|
+
const whole = total / divisor;
|
|
851
|
+
const fraction = (total % divisor).toString().padStart(scale, '0');
|
|
852
|
+
return `${whole.toString()}.${fraction}`;
|
|
853
|
+
}
|
|
854
|
+
function validateRequiredString(name, value) {
|
|
855
|
+
if (!value.trim()) {
|
|
856
|
+
throw new errors_1.ValidationError(`${name} is required`, { [name]: value });
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
//# sourceMappingURL=external-party-wallet.js.map
|