@layerzerolabs/common-canton 1.2.7 → 1.2.9
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/.turbo/turbo-build.log +639 -639
- package/.turbo/turbo-test.log +50 -15
- package/dist/3K5JL2TP.js +71 -0
- package/dist/3K5JL2TP.js.map +1 -0
- package/dist/{SLTAV2JF.js → ANKLDEGA.js} +3 -3
- package/dist/{SLTAV2JF.js.map → ANKLDEGA.js.map} +1 -1
- package/dist/{NRSV2VHQ.js → AWR6H7TM.js} +3 -3
- package/dist/{NRSV2VHQ.js.map → AWR6H7TM.js.map} +1 -1
- package/dist/{ESNOKXKB.js → DGVHAULK.js} +4 -4
- package/dist/{ESNOKXKB.js.map → DGVHAULK.js.map} +1 -1
- package/dist/{4G6A7TFX.js → DSYVONFP.js} +3 -3
- package/dist/{4G6A7TFX.js.map → DSYVONFP.js.map} +1 -1
- package/dist/{ST5U5WZ4.js → FFZGONTK.js} +4 -4
- package/dist/{ST5U5WZ4.js.map → FFZGONTK.js.map} +1 -1
- package/dist/{3ZCDWNJG.js → HTVAEYFB.js} +72 -128
- package/dist/HTVAEYFB.js.map +1 -0
- package/dist/client/amulet.js +2 -2
- package/dist/client/canton-client.d.ts +1 -1
- package/dist/client/canton-client.d.ts.map +1 -1
- package/dist/client/canton-client.js +3 -3
- package/dist/client/grpc.js +2 -2
- package/dist/client/index.js +4 -4
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.js +10 -10
- package/dist/provider.js +5 -5
- package/dist/refresh/index.js +5 -5
- package/dist/refresh/resolve.js +4 -4
- package/dist/sdk/canton-sdk.js +2 -2
- package/dist/sdk/index.js +2 -2
- package/package.json +10 -10
- package/src/client/canton-client.ts +110 -131
- package/src/errors.ts +51 -0
- package/dist/3ZCDWNJG.js.map +0 -1
- package/dist/A7Z5O57E.js +0 -34
- package/dist/A7Z5O57E.js.map +0 -1
|
@@ -2,7 +2,12 @@ import type { ContractId } from '@daml/types';
|
|
|
2
2
|
import { once } from 'es-toolkit';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CantonApiError,
|
|
7
|
+
cantonApiErrorFromResponse,
|
|
8
|
+
CantonProtocolError,
|
|
9
|
+
CantonTransportError,
|
|
10
|
+
} from '../errors';
|
|
6
11
|
import {
|
|
7
12
|
DefaultApi,
|
|
8
13
|
type PostV2CommandsAsyncSubmitRequest,
|
|
@@ -25,7 +30,7 @@ import {
|
|
|
25
30
|
type JsActiveContract,
|
|
26
31
|
JsActiveContractFromJSON,
|
|
27
32
|
} from '../open-api/models';
|
|
28
|
-
import type
|
|
33
|
+
import { type Configuration, ResponseError } from '../open-api/runtime';
|
|
29
34
|
import type {
|
|
30
35
|
ExternalSignature,
|
|
31
36
|
PreparedTransaction,
|
|
@@ -121,33 +126,18 @@ export class CantonClient implements ICantonClient {
|
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
129
|
+
* Run an API call, normalizing a thrown `ResponseError` into a `CantonApiError`
|
|
130
|
+
* that carries `errorCategory` (for retry classification). Other errors pass through.
|
|
126
131
|
*/
|
|
127
|
-
async #
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
async #invoke<T>(call: () => Promise<T>): Promise<T> {
|
|
133
|
+
try {
|
|
134
|
+
return await call();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (err instanceof ResponseError) {
|
|
137
|
+
throw await cantonApiErrorFromResponse(err.response);
|
|
132
138
|
}
|
|
139
|
+
throw err;
|
|
133
140
|
}
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Throw a CantonProtocolError with extracted error details from API response.
|
|
139
|
-
* Handles the common pattern of extracting JSON error details from failed requests.
|
|
140
|
-
*
|
|
141
|
-
* @param err - The caught error
|
|
142
|
-
* @param context - Description of the operation (e.g., "DAR upload", "Party allocation")
|
|
143
|
-
* @throws CantonProtocolError with extracted error details, or re-throws original error
|
|
144
|
-
*/
|
|
145
|
-
async #throwApiError(err: unknown, context: string): Promise<never> {
|
|
146
|
-
const body = await this.#extractErrorBody(err);
|
|
147
|
-
if (body) {
|
|
148
|
-
throw new CantonProtocolError(`${context} failed: ${JSON.stringify(body)}`);
|
|
149
|
-
}
|
|
150
|
-
throw err;
|
|
151
141
|
}
|
|
152
142
|
|
|
153
143
|
/**
|
|
@@ -235,7 +225,7 @@ export class CantonClient implements ICantonClient {
|
|
|
235
225
|
* Returns all connected synchronizer IDs.
|
|
236
226
|
*/
|
|
237
227
|
async connectedSynchronizers(): Promise<string[]> {
|
|
238
|
-
const response = await this.#api.getV2StateConnectedSynchronizers();
|
|
228
|
+
const response = await this.#invoke(() => this.#api.getV2StateConnectedSynchronizers());
|
|
239
229
|
const synchronizers = response.connectedSynchronizers ?? [];
|
|
240
230
|
return synchronizers.map((s) => s.synchronizerId).filter((id): id is string => !!id);
|
|
241
231
|
}
|
|
@@ -283,19 +273,17 @@ export class CantonClient implements ICantonClient {
|
|
|
283
273
|
};
|
|
284
274
|
|
|
285
275
|
try {
|
|
286
|
-
return await this.#api.postV2Dars(request);
|
|
276
|
+
return await this.#invoke(() => this.#api.postV2Dars(request));
|
|
287
277
|
} catch (err: unknown) {
|
|
288
278
|
// If skipIfVetted, treat KNOWN_PACKAGE_VERSION as a no-op success
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
throw new CantonProtocolError(`DAR upload failed: ${JSON.stringify(body)}`);
|
|
296
|
-
}
|
|
279
|
+
if (
|
|
280
|
+
opts.skipIfVetted &&
|
|
281
|
+
err instanceof CantonApiError &&
|
|
282
|
+
err.code === 'KNOWN_PACKAGE_VERSION'
|
|
283
|
+
) {
|
|
284
|
+
return { skipped: true, reason: 'already_vetted', details: err.body };
|
|
297
285
|
}
|
|
298
|
-
throw
|
|
286
|
+
throw err;
|
|
299
287
|
}
|
|
300
288
|
}
|
|
301
289
|
|
|
@@ -321,11 +309,11 @@ export class CantonClient implements ICantonClient {
|
|
|
321
309
|
|
|
322
310
|
if (!request.synchronizerId) {
|
|
323
311
|
return this.#ensureSynchronizerId().then((synchronizerId) =>
|
|
324
|
-
this.#api.postV2DarsValidate({ ...request, synchronizerId }),
|
|
312
|
+
this.#invoke(() => this.#api.postV2DarsValidate({ ...request, synchronizerId })),
|
|
325
313
|
);
|
|
326
314
|
}
|
|
327
315
|
|
|
328
|
-
return this.#api.postV2DarsValidate(request);
|
|
316
|
+
return this.#invoke(() => this.#api.postV2DarsValidate(request));
|
|
329
317
|
}
|
|
330
318
|
|
|
331
319
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -342,10 +330,12 @@ export class CantonClient implements ICantonClient {
|
|
|
342
330
|
async listParties(
|
|
343
331
|
opts: { pageSize?: number; pageToken?: string } = {},
|
|
344
332
|
): Promise<{ parties: string[]; nextPageToken: string }> {
|
|
345
|
-
const response = await this.#
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
333
|
+
const response = await this.#invoke(() =>
|
|
334
|
+
this.#api.getV2Parties({
|
|
335
|
+
pageSize: opts.pageSize,
|
|
336
|
+
pageToken: opts.pageToken,
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
349
339
|
|
|
350
340
|
return {
|
|
351
341
|
parties: response.partyDetails?.map((p) => p.party) ?? [],
|
|
@@ -363,9 +353,11 @@ export class CantonClient implements ICantonClient {
|
|
|
363
353
|
* ourselves to preserve the actual variant payload.
|
|
364
354
|
*/
|
|
365
355
|
async listUserRights(): Promise<UserRight[]> {
|
|
366
|
-
const apiResponse = await this.#
|
|
367
|
-
|
|
368
|
-
|
|
356
|
+
const apiResponse = await this.#invoke(() =>
|
|
357
|
+
this.#api.getV2UsersUserIdRightsRaw({
|
|
358
|
+
userId: this.#userId,
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
369
361
|
const raw = (await apiResponse.raw.json()) as { rights?: UserRight[] };
|
|
370
362
|
return raw.rights ?? [];
|
|
371
363
|
}
|
|
@@ -411,19 +403,16 @@ export class CantonClient implements ICantonClient {
|
|
|
411
403
|
): Promise<string> {
|
|
412
404
|
const synchronizerId = opts.synchronizerId ?? (await this.#ensureSynchronizerId());
|
|
413
405
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
response = await this.#api.postV2Parties({
|
|
406
|
+
const response = await this.#invoke(() =>
|
|
407
|
+
this.#api.postV2Parties({
|
|
417
408
|
allocatePartyRequest: {
|
|
418
409
|
partyIdHint,
|
|
419
410
|
synchronizerId,
|
|
420
411
|
userId: opts.userId ?? '',
|
|
421
412
|
identityProviderId: opts.identityProviderId ?? '',
|
|
422
413
|
},
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
throw await this.#throwApiError(err, 'Party allocation');
|
|
426
|
-
}
|
|
414
|
+
}),
|
|
415
|
+
);
|
|
427
416
|
|
|
428
417
|
const party = response.partyDetails?.party;
|
|
429
418
|
if (!party) {
|
|
@@ -460,12 +449,9 @@ export class CantonClient implements ICantonClient {
|
|
|
460
449
|
},
|
|
461
450
|
};
|
|
462
451
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
} catch (err: unknown) {
|
|
467
|
-
throw await this.#throwApiError(err, 'External party topology generation');
|
|
468
|
-
}
|
|
452
|
+
const response = await this.#invoke(() =>
|
|
453
|
+
this.#api.postV2PartiesExternalGenerateTopology(request),
|
|
454
|
+
);
|
|
469
455
|
|
|
470
456
|
if (!response.partyId || !response.multiHash) {
|
|
471
457
|
throw new CantonProtocolError(
|
|
@@ -513,12 +499,7 @@ export class CantonClient implements ICantonClient {
|
|
|
513
499
|
},
|
|
514
500
|
};
|
|
515
501
|
|
|
516
|
-
|
|
517
|
-
try {
|
|
518
|
-
response = await this.#api.postV2PartiesExternalAllocate(request);
|
|
519
|
-
} catch (err: unknown) {
|
|
520
|
-
throw await this.#throwApiError(err, 'External party allocation');
|
|
521
|
-
}
|
|
502
|
+
const response = await this.#invoke(() => this.#api.postV2PartiesExternalAllocate(request));
|
|
522
503
|
|
|
523
504
|
if (!response.partyId) {
|
|
524
505
|
throw new CantonProtocolError('allocateExternalParty response missing partyId');
|
|
@@ -534,18 +515,16 @@ export class CantonClient implements ICantonClient {
|
|
|
534
515
|
* POST /v2/users/{userId}/rights
|
|
535
516
|
*/
|
|
536
517
|
async grantCanActAs(partyId: string): Promise<void> {
|
|
537
|
-
|
|
538
|
-
|
|
518
|
+
await this.#invoke(() =>
|
|
519
|
+
this.#api.postV2UsersUserIdRights({
|
|
539
520
|
userId: this.#userId,
|
|
540
521
|
grantUserRightsRequest: {
|
|
541
522
|
userId: this.#userId,
|
|
542
523
|
rights: [{ kind: { canActAs: { value: { party: partyId } } } }],
|
|
543
524
|
identityProviderId: '',
|
|
544
525
|
},
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
throw await this.#throwApiError(err, `Grant canActAs for party ${partyId}`);
|
|
548
|
-
}
|
|
526
|
+
}),
|
|
527
|
+
);
|
|
549
528
|
}
|
|
550
529
|
|
|
551
530
|
/**
|
|
@@ -575,12 +554,9 @@ export class CantonClient implements ICantonClient {
|
|
|
575
554
|
},
|
|
576
555
|
};
|
|
577
556
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
} catch (err: unknown) {
|
|
582
|
-
throw await this.#throwApiError(err, 'Package lookup');
|
|
583
|
-
}
|
|
557
|
+
const response = await this.#invoke(() =>
|
|
558
|
+
this.#api.postV2InteractiveSubmissionPreferredPackages(request),
|
|
559
|
+
);
|
|
584
560
|
|
|
585
561
|
const packageRef = response.packageReferences?.find(
|
|
586
562
|
(ref) => ref.packageName === packageName,
|
|
@@ -612,7 +588,7 @@ export class CantonClient implements ICantonClient {
|
|
|
612
588
|
const commandId = opts.commandId ?? randomUUID();
|
|
613
589
|
|
|
614
590
|
// Snapshot ledger end before submission so waitForCommand knows where to start polling
|
|
615
|
-
const ledgerEndResponse = await this.#api.getV2StateLedgerEnd();
|
|
591
|
+
const ledgerEndResponse = await this.#invoke(() => this.#api.getV2StateLedgerEnd());
|
|
616
592
|
const completionOffset = ledgerEndResponse.offset;
|
|
617
593
|
|
|
618
594
|
const request: PostV2CommandsAsyncSubmitRequest = {
|
|
@@ -627,11 +603,7 @@ export class CantonClient implements ICantonClient {
|
|
|
627
603
|
},
|
|
628
604
|
};
|
|
629
605
|
|
|
630
|
-
|
|
631
|
-
await this.#api.postV2CommandsAsyncSubmit(request);
|
|
632
|
-
} catch (err: unknown) {
|
|
633
|
-
throw await this.#throwApiError(err, 'Command submission');
|
|
634
|
-
}
|
|
606
|
+
await this.#invoke(() => this.#api.postV2CommandsAsyncSubmit(request));
|
|
635
607
|
|
|
636
608
|
return { commandId, completionOffset };
|
|
637
609
|
}
|
|
@@ -669,7 +641,9 @@ export class CantonClient implements ICantonClient {
|
|
|
669
641
|
streamIdleTimeoutMs,
|
|
670
642
|
};
|
|
671
643
|
|
|
672
|
-
const rawResponse = await this.#
|
|
644
|
+
const rawResponse = await this.#invoke(() =>
|
|
645
|
+
this.#api.postV2CommandsCompletionsRaw(request),
|
|
646
|
+
);
|
|
673
647
|
const responseText = await rawResponse.raw.text();
|
|
674
648
|
const completions = JSON.parse(responseText) as unknown[];
|
|
675
649
|
|
|
@@ -782,7 +756,9 @@ export class CantonClient implements ICantonClient {
|
|
|
782
756
|
},
|
|
783
757
|
};
|
|
784
758
|
|
|
785
|
-
const response = await this.#
|
|
759
|
+
const response = await this.#invoke(() =>
|
|
760
|
+
this.#api.postV2EventsEventsByContractId(request),
|
|
761
|
+
);
|
|
786
762
|
|
|
787
763
|
// If archived, contract is not active
|
|
788
764
|
if (response.archived) return undefined;
|
|
@@ -849,7 +825,9 @@ export class CantonClient implements ICantonClient {
|
|
|
849
825
|
},
|
|
850
826
|
};
|
|
851
827
|
|
|
852
|
-
const response = await this.#
|
|
828
|
+
const response = await this.#invoke(() =>
|
|
829
|
+
this.#api.postV2EventsEventsByContractId(request),
|
|
830
|
+
);
|
|
853
831
|
|
|
854
832
|
return {
|
|
855
833
|
created: response.created?.createdEvent,
|
|
@@ -867,12 +845,14 @@ export class CantonClient implements ICantonClient {
|
|
|
867
845
|
updateId: string,
|
|
868
846
|
parties: string[],
|
|
869
847
|
): Promise<CantonTransaction> {
|
|
870
|
-
const response = await this.#
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
848
|
+
const response = await this.#invoke(() =>
|
|
849
|
+
this.#api.postV2UpdatesUpdateByIdRaw({
|
|
850
|
+
getUpdateByIdRequest: {
|
|
851
|
+
updateId,
|
|
852
|
+
updateFormat: this.#buildUpdateFormat(parties),
|
|
853
|
+
},
|
|
854
|
+
}),
|
|
855
|
+
);
|
|
876
856
|
|
|
877
857
|
const updateResponse: CantonUpdateResponse = await response.raw.json();
|
|
878
858
|
return this.#parseTransactionFromUpdate(
|
|
@@ -888,12 +868,14 @@ export class CantonClient implements ICantonClient {
|
|
|
888
868
|
* @returns The full transaction including recordTime
|
|
889
869
|
*/
|
|
890
870
|
async fetchTransactionByOffset(offset: number, parties: string[]): Promise<CantonTransaction> {
|
|
891
|
-
const response = await this.#
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
871
|
+
const response = await this.#invoke(() =>
|
|
872
|
+
this.#api.postV2UpdatesUpdateByOffsetRaw({
|
|
873
|
+
getUpdateByOffsetRequest: {
|
|
874
|
+
offset,
|
|
875
|
+
updateFormat: this.#buildUpdateFormat(parties),
|
|
876
|
+
},
|
|
877
|
+
}),
|
|
878
|
+
);
|
|
897
879
|
|
|
898
880
|
const updateResponse: CantonUpdateResponse = await response.raw.json();
|
|
899
881
|
return this.#parseTransactionFromUpdate(
|
|
@@ -943,12 +925,9 @@ export class CantonClient implements ICantonClient {
|
|
|
943
925
|
},
|
|
944
926
|
};
|
|
945
927
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
} catch (error: unknown) {
|
|
950
|
-
throw await this.#throwApiError(error, 'Interactive submission prepare');
|
|
951
|
-
}
|
|
928
|
+
const response = await this.#invoke(() =>
|
|
929
|
+
this.#api.postV2InteractiveSubmissionPrepare(request),
|
|
930
|
+
);
|
|
952
931
|
const validated = this.#validatePrepareResponse(response);
|
|
953
932
|
|
|
954
933
|
return {
|
|
@@ -969,7 +948,7 @@ export class CantonClient implements ICantonClient {
|
|
|
969
948
|
signatures: ExternalSignature[],
|
|
970
949
|
): Promise<SubmitResult> {
|
|
971
950
|
// Snapshot ledger end so the caller can poll for completion from this point
|
|
972
|
-
const ledgerEndResponse = await this.#api.getV2StateLedgerEnd();
|
|
951
|
+
const ledgerEndResponse = await this.#invoke(() => this.#api.getV2StateLedgerEnd());
|
|
973
952
|
const completionOffset = ledgerEndResponse.offset;
|
|
974
953
|
|
|
975
954
|
const request: PostV2InteractiveSubmissionExecuteRequest = {
|
|
@@ -1000,11 +979,7 @@ export class CantonClient implements ICantonClient {
|
|
|
1000
979
|
},
|
|
1001
980
|
};
|
|
1002
981
|
|
|
1003
|
-
|
|
1004
|
-
await this.#api.postV2InteractiveSubmissionExecute(request);
|
|
1005
|
-
} catch (error: unknown) {
|
|
1006
|
-
throw await this.#throwApiError(error, 'Interactive submission execute');
|
|
1007
|
-
}
|
|
982
|
+
await this.#invoke(() => this.#api.postV2InteractiveSubmissionExecute(request));
|
|
1008
983
|
|
|
1009
984
|
return { commandId: prepared.commandId, completionOffset };
|
|
1010
985
|
}
|
|
@@ -1017,7 +992,7 @@ export class CantonClient implements ICantonClient {
|
|
|
1017
992
|
* Get the current ledger end offset.
|
|
1018
993
|
*/
|
|
1019
994
|
async getLedgerEndOffset(): Promise<number> {
|
|
1020
|
-
const response = await this.#api.getV2StateLedgerEnd();
|
|
995
|
+
const response = await this.#invoke(() => this.#api.getV2StateLedgerEnd());
|
|
1021
996
|
return response.offset;
|
|
1022
997
|
}
|
|
1023
998
|
|
|
@@ -1096,14 +1071,16 @@ export class CantonClient implements ICantonClient {
|
|
|
1096
1071
|
parties.map((party) => [party, { cumulative: cumulativeFilter }]),
|
|
1097
1072
|
);
|
|
1098
1073
|
|
|
1099
|
-
const rawResponse = await this.#
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1074
|
+
const rawResponse = await this.#invoke(() =>
|
|
1075
|
+
this.#api.postV2StateActiveContractsRaw({
|
|
1076
|
+
getActiveContractsRequest: {
|
|
1077
|
+
filter: { filtersByParty },
|
|
1078
|
+
verbose: true,
|
|
1079
|
+
activeAtOffset: offset,
|
|
1080
|
+
},
|
|
1081
|
+
limit: opts?.limit,
|
|
1082
|
+
}),
|
|
1083
|
+
);
|
|
1107
1084
|
|
|
1108
1085
|
// Raw response is an array of streamed entries; extract and deserialize contracts
|
|
1109
1086
|
type RawActiveContractsResponse = Array<{
|
|
@@ -1144,20 +1121,22 @@ export class CantonClient implements ICantonClient {
|
|
|
1144
1121
|
},
|
|
1145
1122
|
verbose: false,
|
|
1146
1123
|
};
|
|
1147
|
-
const rawResponse = await this.#
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1124
|
+
const rawResponse = await this.#invoke(() =>
|
|
1125
|
+
this.#api.postV2UpdatesRaw({
|
|
1126
|
+
getUpdatesRequest: {
|
|
1127
|
+
beginExclusive,
|
|
1128
|
+
endInclusive,
|
|
1129
|
+
verbose: false,
|
|
1130
|
+
updateFormat: {
|
|
1131
|
+
includeTransactions: {
|
|
1132
|
+
eventFormat,
|
|
1133
|
+
transactionShape: 'TRANSACTION_SHAPE_LEDGER_EFFECTS' as const,
|
|
1134
|
+
},
|
|
1156
1135
|
},
|
|
1157
1136
|
},
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1137
|
+
limit: opts?.limit,
|
|
1138
|
+
}),
|
|
1139
|
+
);
|
|
1161
1140
|
|
|
1162
1141
|
const updateResponses: CantonUpdateResponse[] = await rawResponse.raw.json();
|
|
1163
1142
|
|
package/src/errors.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Base error class for Canton SDK errors.
|
|
3
5
|
*/
|
|
@@ -31,3 +33,52 @@ export class CantonProtocolError extends CantonError {
|
|
|
31
33
|
super(message);
|
|
32
34
|
}
|
|
33
35
|
}
|
|
36
|
+
|
|
37
|
+
/** A JSON Ledger API error, normalized from a non-2xx response's `JsCantonError` body. */
|
|
38
|
+
export class CantonApiError extends CantonError {
|
|
39
|
+
constructor(
|
|
40
|
+
message: string,
|
|
41
|
+
/** Canton error category (1–14). See {@link isRetryableCantonError}. */
|
|
42
|
+
public readonly errorCategory: number | undefined,
|
|
43
|
+
/** Canton error code id, e.g. `CONTRACT_NOT_FOUND`. */
|
|
44
|
+
public readonly code: string | undefined,
|
|
45
|
+
public readonly status: number,
|
|
46
|
+
public readonly body: unknown,
|
|
47
|
+
) {
|
|
48
|
+
super(message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const cantonApiErrorBodySchema = z.object({
|
|
53
|
+
errorCategory: z.number(),
|
|
54
|
+
code: z.string(),
|
|
55
|
+
cause: z.string(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/** Build a {@link CantonApiError} from a non-2xx `Response`, reading a clone of the body. */
|
|
59
|
+
export async function cantonApiErrorFromResponse(response: Response): Promise<CantonApiError> {
|
|
60
|
+
const body = await response.json().catch(() => undefined);
|
|
61
|
+
const fields = cantonApiErrorBodySchema.safeParse(body).data;
|
|
62
|
+
const message = `Canton API error ${response.status}${fields?.code ? ` [${fields.code}]` : ''}${
|
|
63
|
+
fields?.cause ? `: ${fields.cause}` : ''
|
|
64
|
+
}`;
|
|
65
|
+
return new CantonApiError(message, fields?.errorCategory, fields?.code, response.status, body);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Canton's `errorCategory` (1–14) is the retry signal, not the HTTP status.
|
|
69
|
+
// See https://docs.canton.network/appdev/reference/error-codes
|
|
70
|
+
const NON_RETRYABLE_CANTON_ERROR_CATEGORIES: ReadonlySet<number> = new Set([
|
|
71
|
+
5, 9, 10, 11, 12, 13, 14,
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Non-retryable Categories: 5, 9, 10, 11, 12, 13, 14. Everything else is retryable.
|
|
76
|
+
* Errors with no category (transport/connection failures) are treated as retryable.
|
|
77
|
+
*/
|
|
78
|
+
export function isRetryableCantonError(error: unknown): boolean {
|
|
79
|
+
return (
|
|
80
|
+
!(error instanceof CantonApiError) ||
|
|
81
|
+
error.errorCategory === undefined ||
|
|
82
|
+
!NON_RETRYABLE_CANTON_ERROR_CATEGORIES.has(error.errorCategory)
|
|
83
|
+
);
|
|
84
|
+
}
|