@layerzerolabs/common-canton 0.2.110 → 0.2.112
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 +837 -821
- package/.turbo/turbo-test.log +22 -19
- package/dist/{PVONQCBG.js → 2UKHG3HM.js} +3 -3
- package/dist/{PVONQCBG.js.map → 2UKHG3HM.js.map} +1 -1
- package/dist/{GMQEAD3I.js → 34I4OGQK.js} +3 -3
- package/dist/{GMQEAD3I.js.map → 34I4OGQK.js.map} +1 -1
- package/dist/{NLMU226B.js → 45YQNHS2.js} +3 -3
- package/dist/{NLMU226B.js.map → 45YQNHS2.js.map} +1 -1
- package/dist/{HNZRSDTR.js → 4CJTJBWF.js} +15 -46
- package/dist/4CJTJBWF.js.map +1 -0
- package/dist/{KK2NBQSG.js → 4GYCD3G4.js} +3 -3
- package/dist/{KK2NBQSG.js.map → 4GYCD3G4.js.map} +1 -1
- package/dist/{EIXIZULE.js → 5HH7PZW3.js} +37 -37
- package/dist/{EIXIZULE.js.map → 5HH7PZW3.js.map} +1 -1
- package/dist/{OQ2FVATH.js → 5OA7RJHH.js} +3 -3
- package/dist/{OQ2FVATH.js.map → 5OA7RJHH.js.map} +1 -1
- package/dist/{QYQCAXCB.js → 73I7CBP3.js} +3 -3
- package/dist/{QYQCAXCB.js.map → 73I7CBP3.js.map} +1 -1
- package/dist/{MDEOBKC3.js → 7GI6QGS6.js} +3 -3
- package/dist/{MDEOBKC3.js.map → 7GI6QGS6.js.map} +1 -1
- package/dist/{IOTRKLGP.js → 7STDATVZ.js} +3 -3
- package/dist/{IOTRKLGP.js.map → 7STDATVZ.js.map} +1 -1
- package/dist/{T4AL4FJH.js → ALC4M7WK.js} +3 -3
- package/dist/{T4AL4FJH.js.map → ALC4M7WK.js.map} +1 -1
- package/dist/{TWUSN2QY.js → AMLJTQWB.js} +3 -3
- package/dist/{TWUSN2QY.js.map → AMLJTQWB.js.map} +1 -1
- package/dist/{YCNGW63C.js → BL22CZFA.js} +3 -3
- package/dist/{YCNGW63C.js.map → BL22CZFA.js.map} +1 -1
- package/dist/{ZN2CNFMH.js → BLWJ63ZZ.js} +3 -3
- package/dist/{ZN2CNFMH.js.map → BLWJ63ZZ.js.map} +1 -1
- package/dist/CJ2WHLYC.js +116 -0
- package/dist/CJ2WHLYC.js.map +1 -0
- package/dist/{4AKFOZFY.js → CLAE73FR.js} +3 -3
- package/dist/{4AKFOZFY.js.map → CLAE73FR.js.map} +1 -1
- package/dist/CMDFOA7C.js +47 -0
- package/dist/CMDFOA7C.js.map +1 -0
- package/dist/CXTOAS7N.js +18 -0
- package/dist/CXTOAS7N.js.map +1 -0
- package/dist/{NX67V54F.js → DAP5XJGH.js} +3 -3
- package/dist/{NX67V54F.js.map → DAP5XJGH.js.map} +1 -1
- package/dist/{PSB6BQFH.js → EYLCZMM4.js} +5 -4
- package/dist/EYLCZMM4.js.map +1 -0
- package/dist/FPRRDKT5.js +52 -0
- package/dist/FPRRDKT5.js.map +1 -0
- package/dist/{KJRFPBY4.js → FXR2Z54G.js} +3 -3
- package/dist/{KJRFPBY4.js.map → FXR2Z54G.js.map} +1 -1
- package/dist/{MJCI4XZ2.js → HLWLB4AY.js} +3 -3
- package/dist/{MJCI4XZ2.js.map → HLWLB4AY.js.map} +1 -1
- package/dist/{XSEEW6TB.js → HRZTWWO4.js} +3 -3
- package/dist/{XSEEW6TB.js.map → HRZTWWO4.js.map} +1 -1
- package/dist/{QHEXOE3Y.js → JI3XNGXO.js} +3 -27
- package/dist/JI3XNGXO.js.map +1 -0
- package/dist/{EPHLTMJZ.js → JSG7MGHH.js} +3 -3
- package/dist/{EPHLTMJZ.js.map → JSG7MGHH.js.map} +1 -1
- package/dist/{JH2I4JGJ.js → JXKVPYCE.js} +3 -3
- package/dist/{JH2I4JGJ.js.map → JXKVPYCE.js.map} +1 -1
- package/dist/{4VLNRPYZ.js → K2SFNQJC.js} +5 -5
- package/dist/{4VLNRPYZ.js.map → K2SFNQJC.js.map} +1 -1
- package/dist/{H7FW24YI.js → KBZ6YXX4.js} +3 -3
- package/dist/{H7FW24YI.js.map → KBZ6YXX4.js.map} +1 -1
- package/dist/{ZF6ASBPX.js → KH7RP6Q4.js} +3 -3
- package/dist/{ZF6ASBPX.js.map → KH7RP6Q4.js.map} +1 -1
- package/dist/{AV2FN74T.js → KTNEUXUI.js} +4 -4
- package/dist/{AV2FN74T.js.map → KTNEUXUI.js.map} +1 -1
- package/dist/{D3AV6J7R.js → LL7Z56UN.js} +3 -3
- package/dist/{D3AV6J7R.js.map → LL7Z56UN.js.map} +1 -1
- package/dist/{AYAFFPBV.js → MFVQ6EHM.js} +3 -3
- package/dist/{AYAFFPBV.js.map → MFVQ6EHM.js.map} +1 -1
- package/dist/{U7ISOK6B.js → NKXEPWZV.js} +3 -3
- package/dist/{U7ISOK6B.js.map → NKXEPWZV.js.map} +1 -1
- package/dist/{P3A2XIFY.js → NS763D57.js} +3 -3
- package/dist/{P3A2XIFY.js.map → NS763D57.js.map} +1 -1
- package/dist/{3UD2GTXW.js → PGQCNYFD.js} +3 -3
- package/dist/{3UD2GTXW.js.map → PGQCNYFD.js.map} +1 -1
- package/dist/{2UWZKS5E.js → Q3RL6UXD.js} +4 -4
- package/dist/{2UWZKS5E.js.map → Q3RL6UXD.js.map} +1 -1
- package/dist/{5KFMHJZB.js → QYQWHMQM.js} +3 -3
- package/dist/{5KFMHJZB.js.map → QYQWHMQM.js.map} +1 -1
- package/dist/{BJXKGZ5Y.js → QZJONP6W.js} +3 -3
- package/dist/{BJXKGZ5Y.js.map → QZJONP6W.js.map} +1 -1
- package/dist/{X66QZBHT.js → SLTAV2JF.js} +9 -2
- package/dist/SLTAV2JF.js.map +1 -0
- package/dist/{JV2DWUBW.js → SMLSAWCA.js} +48 -4
- package/dist/SMLSAWCA.js.map +1 -0
- package/dist/{RQXQCK22.js → UBHUOSK3.js} +3 -3
- package/dist/{RQXQCK22.js.map → UBHUOSK3.js.map} +1 -1
- package/dist/{OMRF3GH2.js → UO7AO4TN.js} +3 -3
- package/dist/{OMRF3GH2.js.map → UO7AO4TN.js.map} +1 -1
- package/dist/{R26MH4UH.js → UQMKPY2S.js} +3 -3
- package/dist/{R26MH4UH.js.map → UQMKPY2S.js.map} +1 -1
- package/dist/{HU7X7G5L.js → WSLU3JGO.js} +3 -3
- package/dist/{HU7X7G5L.js.map → WSLU3JGO.js.map} +1 -1
- package/dist/{WIQFSNER.js → XINHRIWA.js} +3 -3
- package/dist/{WIQFSNER.js.map → XINHRIWA.js.map} +1 -1
- package/dist/{PHVMGN7T.js → ZGWBZC76.js} +3 -3
- package/dist/{PHVMGN7T.js.map → ZGWBZC76.js.map} +1 -1
- package/dist/{AE56UC5I.js → ZPOY5WJ2.js} +3 -3
- package/dist/{AE56UC5I.js.map → ZPOY5WJ2.js.map} +1 -1
- package/dist/amulet.d.ts +7 -0
- package/dist/amulet.d.ts.map +1 -0
- package/dist/amulet.js +4 -0
- package/dist/amulet.js.map +1 -0
- package/dist/auth/index.js +1 -1
- package/dist/client/canton-client.d.ts +12 -0
- package/dist/client/canton-client.d.ts.map +1 -1
- package/dist/client/canton-client.js +138 -138
- package/dist/client/index.js +140 -139
- package/dist/client/interface.d.ts +12 -0
- package/dist/client/interface.d.ts.map +1 -1
- package/dist/client/utils.d.ts.map +1 -1
- package/dist/client/utils.js +2 -1
- package/dist/crypto.d.ts +23 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +4 -0
- package/dist/crypto.js.map +1 -0
- package/dist/daml-serde.d.ts +73 -0
- package/dist/daml-serde.d.ts.map +1 -0
- package/dist/daml-serde.js +4 -0
- package/dist/daml-serde.js.map +1 -0
- package/dist/events.d.ts +9 -36
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +2 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +150 -147
- package/dist/open-api/apis/DefaultApi.js +137 -137
- package/dist/open-api/apis/index.js +137 -137
- package/dist/open-api/index.js +137 -137
- package/dist/open-api/models/Command.js +3 -3
- package/dist/open-api/models/Command1.js +2 -2
- package/dist/open-api/models/Completion.js +3 -3
- package/dist/open-api/models/Completion1.js +3 -3
- package/dist/open-api/models/CompletionResponse.js +7 -7
- package/dist/open-api/models/CompletionResponseOneOf.js +3 -3
- package/dist/open-api/models/CompletionStreamResponse.js +7 -7
- package/dist/open-api/models/CreateUserRequest.js +10 -10
- package/dist/open-api/models/CumulativeFilter.js +2 -2
- package/dist/open-api/models/DeduplicationPeriod.js +1 -1
- package/dist/open-api/models/DeduplicationPeriod1.js +1 -1
- package/dist/open-api/models/DeduplicationPeriod2.js +3 -3
- package/dist/open-api/models/EventFormat.js +2 -2
- package/dist/open-api/models/FeaturesDescriptor.js +2 -2
- package/dist/open-api/models/Filters.js +2 -2
- package/dist/open-api/models/GetActiveContractsRequest.js +2 -2
- package/dist/open-api/models/GetEventsByContractIdRequest.js +2 -2
- package/dist/open-api/models/GetLedgerApiVersionResponse.js +3 -3
- package/dist/open-api/models/GetTransactionByIdRequest.js +2 -2
- package/dist/open-api/models/GetTransactionByOffsetRequest.js +2 -2
- package/dist/open-api/models/GetUpdateByIdRequest.js +2 -2
- package/dist/open-api/models/GetUpdateByOffsetRequest.js +2 -2
- package/dist/open-api/models/GetUpdatesRequest.js +2 -2
- package/dist/open-api/models/GrantUserRightsRequest.js +10 -10
- package/dist/open-api/models/GrantUserRightsResponse.js +10 -10
- package/dist/open-api/models/IdentifierFilter.js +2 -2
- package/dist/open-api/models/JsCommands.js +5 -5
- package/dist/open-api/models/JsContractEntry.js +3 -3
- package/dist/open-api/models/JsExecuteSubmissionAndWaitForTransactionRequest.js +7 -7
- package/dist/open-api/models/JsExecuteSubmissionAndWaitRequest.js +5 -5
- package/dist/open-api/models/JsExecuteSubmissionRequest.js +5 -5
- package/dist/open-api/models/JsGetActiveContractsResponse.js +4 -4
- package/dist/open-api/models/JsGetUpdateResponse.js +13 -13
- package/dist/open-api/models/JsGetUpdateTreesResponse.js +5 -5
- package/dist/open-api/models/JsGetUpdatesResponse.js +13 -13
- package/dist/open-api/models/JsPrepareSubmissionRequest.js +5 -5
- package/dist/open-api/models/JsReassignment.js +1 -1
- package/dist/open-api/models/JsReassignmentEvent.js +1 -1
- package/dist/open-api/models/JsSubmitAndWaitForReassignmentResponse.js +1 -1
- package/dist/open-api/models/JsSubmitAndWaitForTransactionRequest.js +9 -9
- package/dist/open-api/models/JsTopologyTransaction.js +6 -6
- package/dist/open-api/models/Kind.js +8 -8
- package/dist/open-api/models/ListUserRightsResponse.js +10 -10
- package/dist/open-api/models/Operation.js +4 -4
- package/dist/open-api/models/Reassignment.js +1 -1
- package/dist/open-api/models/Reassignment1.js +1 -1
- package/dist/open-api/models/ReassignmentCommand.js +3 -3
- package/dist/open-api/models/ReassignmentCommands.js +4 -4
- package/dist/open-api/models/RevokeUserRightsRequest.js +10 -10
- package/dist/open-api/models/RevokeUserRightsResponse.js +10 -10
- package/dist/open-api/models/Right.js +9 -9
- package/dist/open-api/models/SubmitAndWaitForReassignmentRequest.js +7 -7
- package/dist/open-api/models/SubmitReassignmentRequest.js +5 -5
- package/dist/open-api/models/TopologyEvent.js +5 -5
- package/dist/open-api/models/TopologyEventEvent.js +4 -4
- package/dist/open-api/models/TopologyTransaction.js +7 -7
- package/dist/open-api/models/TransactionFilter.js +2 -2
- package/dist/open-api/models/TransactionFormat.js +2 -2
- package/dist/open-api/models/Update.js +12 -12
- package/dist/open-api/models/Update1.js +4 -4
- package/dist/open-api/models/Update1OneOf1.js +1 -1
- package/dist/open-api/models/UpdateFormat.js +2 -2
- package/dist/open-api/models/UpdateOneOf1.js +1 -1
- package/dist/open-api/models/UpdateOneOf2.js +8 -8
- package/dist/open-api/models/UpdateVettedPackagesRequest.js +6 -6
- package/dist/open-api/models/VettedPackagesChange.js +5 -5
- package/dist/open-api/models/index.js +136 -136
- package/dist/provider.d.ts +11 -27
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +140 -140
- package/dist/sdk/canton-sdk.d.ts +7 -0
- package/dist/sdk/canton-sdk.d.ts.map +1 -1
- package/dist/sdk/canton-sdk.js +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/transaction.d.ts +64 -6
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +1 -1
- package/package.json +13 -10
- package/src/amulet.ts +28 -0
- package/src/client/canton-client.ts +60 -1
- package/src/client/interface.ts +16 -0
- package/src/client/utils.ts +1 -6
- package/src/crypto.ts +42 -0
- package/src/daml-serde.ts +182 -0
- package/src/events.ts +31 -155
- package/src/index.ts +3 -0
- package/src/provider.ts +22 -82
- package/src/sdk/canton-sdk.ts +13 -0
- package/src/transaction.ts +4 -0
- package/test/crypto.test.ts +52 -0
- package/test/daml-serde.test.ts +140 -0
- package/test/mocks/scripts/capture-disclosure.ts +5 -2
- package/test/mocks/scripts/capture-external-requests.ts +5 -2
- package/test/mocks/scripts/capture-unhappy-paths.ts +5 -2
- package/test/provider.test.ts +47 -36
- package/dist/GXJEEYPP.js +0 -104
- package/dist/GXJEEYPP.js.map +0 -1
- package/dist/HNZRSDTR.js.map +0 -1
- package/dist/JV2DWUBW.js.map +0 -1
- package/dist/PSB6BQFH.js.map +0 -1
- package/dist/QHEXOE3Y.js.map +0 -1
- package/dist/X66QZBHT.js.map +0 -1
package/src/events.ts
CHANGED
|
@@ -1,37 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
1
|
+
import { NonEmittableError } from '@layerzerolabs/common-error-utils';
|
|
3
2
|
|
|
4
3
|
import type { CantonTransaction, ICantonClient } from './client';
|
|
5
|
-
import type { CreatedEvent } from './open-api/models';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const cantonTxWithResultSchema = buildTransactionWithResultSchema(cantonTransactionSchema);
|
|
9
|
-
|
|
10
|
-
// TODO(canton): confirm these poll values are enough to wait for archival across environments.
|
|
11
|
-
const REQUEST_ARCHIVAL_POLL_INTERVAL_MS = 2000;
|
|
12
|
-
const REQUEST_ARCHIVAL_MAX_ATTEMPTS = 60;
|
|
13
|
-
|
|
14
|
-
// IRequest interface choices that consume the Request contract via `archive reqCid` in their impls.
|
|
15
|
-
const IREQUEST_ACCEPT_CHOICE = 'IRequest_Accept';
|
|
16
|
-
const IREQUEST_REJECT_CHOICE = 'IRequest_Reject';
|
|
17
|
-
|
|
18
|
-
const REQUEST_TEMPLATE_QUALIFIED_NAME = 'Request:Request';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Fetches a transaction by updateId and returns all CreatedEvents.
|
|
22
|
-
*/
|
|
23
|
-
export const fetchCreatedEvents = async (
|
|
24
|
-
client: ICantonClient,
|
|
25
|
-
updateId: string,
|
|
26
|
-
parties: string[],
|
|
27
|
-
): Promise<CreatedEvent[]> => {
|
|
28
|
-
const cantonTx = await client.fetchTransactionByUpdateId(updateId, parties);
|
|
29
|
-
return (
|
|
30
|
-
cantonTx.events
|
|
31
|
-
?.filter((event): event is { CreatedEvent: CreatedEvent } => 'CreatedEvent' in event)
|
|
32
|
-
.map((event) => event.CreatedEvent) ?? []
|
|
33
|
-
);
|
|
34
|
-
};
|
|
4
|
+
import type { CreatedEvent, ExercisedEvent } from './open-api/models';
|
|
5
|
+
import { cantonTransactionWithResultSchema } from './transaction';
|
|
35
6
|
|
|
36
7
|
/**
|
|
37
8
|
* Finds a CreatedEvent matching the expected templateId whose signatories
|
|
@@ -50,6 +21,22 @@ export const findMatchingCreatedEvent = (
|
|
|
50
21
|
});
|
|
51
22
|
};
|
|
52
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Fetches a transaction by updateId and returns all CreatedEvents.
|
|
26
|
+
*/
|
|
27
|
+
export const fetchCreatedEvents = async (
|
|
28
|
+
client: ICantonClient,
|
|
29
|
+
updateId: string,
|
|
30
|
+
parties: string[],
|
|
31
|
+
): Promise<CreatedEvent[]> => {
|
|
32
|
+
const cantonTx = await client.fetchTransactionByUpdateId(updateId, parties);
|
|
33
|
+
return (
|
|
34
|
+
cantonTx.events
|
|
35
|
+
?.filter((event): event is { CreatedEvent: CreatedEvent } => 'CreatedEvent' in event)
|
|
36
|
+
.map((event) => event.CreatedEvent) ?? []
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
53
40
|
/**
|
|
54
41
|
* Parses a Canton transaction-with-result, fetches the created events from the ledger,
|
|
55
42
|
* and returns the first CreatedEvent matching the submitted command's templateId and actAs
|
|
@@ -59,7 +46,7 @@ export const findMatchingCreatedEventFromTx = async (
|
|
|
59
46
|
client: ICantonClient,
|
|
60
47
|
tx: unknown,
|
|
61
48
|
): Promise<CreatedEvent | null> => {
|
|
62
|
-
const parsed =
|
|
49
|
+
const parsed = cantonTransactionWithResultSchema.safeParse(tx);
|
|
63
50
|
if (!parsed.success) return null;
|
|
64
51
|
|
|
65
52
|
if (parsed.data.result.type !== 'success') return null;
|
|
@@ -93,142 +80,31 @@ export const cantonDeploymentSucceeded = async (
|
|
|
93
80
|
};
|
|
94
81
|
|
|
95
82
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* Positively confirms acceptance: locate the Request created by this submission,
|
|
100
|
-
* then delegate to waitForRequestArchival, which waits for the Request to be
|
|
101
|
-
* archived and verifies the IRequest_Accept choice ran (throwing on reject,
|
|
102
|
-
* unexpected archival, or timeout). This is a positive accept check rather than
|
|
103
|
-
* inferring success from the absence of a rejection event.
|
|
104
|
-
*/
|
|
105
|
-
export const cantonRegisterSucceeded = async (
|
|
106
|
-
client: ICantonClient,
|
|
107
|
-
txWithResult: unknown,
|
|
108
|
-
): Promise<boolean> => {
|
|
109
|
-
const parsed = cantonTxWithResultSchema.safeParse(txWithResult);
|
|
110
|
-
if (!parsed.success || parsed.data.result.type !== 'success') return false;
|
|
111
|
-
|
|
112
|
-
const updateId = parsed.data.result.minedTxHash;
|
|
113
|
-
if (!updateId) return false;
|
|
114
|
-
|
|
115
|
-
// Deferred-sender register payloads build with empty actAs (the sender is
|
|
116
|
-
// resolved at sign time), so fall back to the parties this user can read as.
|
|
117
|
-
const { actAs } = parsed.data.populatedTransaction;
|
|
118
|
-
const parties = actAs.length ? actAs : await client.listUserParties();
|
|
119
|
-
if (!parties.length) return false;
|
|
120
|
-
|
|
121
|
-
const createdEvents = await fetchCreatedEvents(client, updateId, parties);
|
|
122
|
-
const requestEvent = createdEvents.find((event) =>
|
|
123
|
-
event.templateId?.includes(REQUEST_TEMPLATE_QUALIFIED_NAME),
|
|
124
|
-
);
|
|
125
|
-
if (!requestEvent) return false;
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
await waitForRequestArchival(client, requestEvent.contractId, parties);
|
|
129
|
-
return true;
|
|
130
|
-
} catch {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Poll until `contractId` is archived, then return the transaction tree that
|
|
137
|
-
* archived it. Generic: knows nothing about which choice did the archiving.
|
|
138
|
-
* Throws NonRetryableError on timeout.
|
|
83
|
+
* Waits for a contract to be archived and returns the transaction that archived it.
|
|
84
|
+
* Throws NonEmittableError if the contract is not yet archived.
|
|
139
85
|
*/
|
|
140
86
|
export const waitForArchival = async (
|
|
141
87
|
client: ICantonClient,
|
|
142
88
|
contractId: string,
|
|
143
89
|
parties: string[],
|
|
144
90
|
): Promise<CantonTransaction> => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return client.fetchTransactionByOffset(archived.offset, parties);
|
|
149
|
-
}
|
|
150
|
-
// Skip the sleep on the final attempt — there's no next iteration to wait for.
|
|
151
|
-
if (attempt < REQUEST_ARCHIVAL_MAX_ATTEMPTS) {
|
|
152
|
-
await new Promise((resolve) => setTimeout(resolve, REQUEST_ARCHIVAL_POLL_INTERVAL_MS));
|
|
153
|
-
}
|
|
91
|
+
const { archived } = await client.fetchEventsByContractId(contractId, parties);
|
|
92
|
+
if (archived) {
|
|
93
|
+
return client.fetchTransactionByOffset(archived.offset, parties);
|
|
154
94
|
}
|
|
155
|
-
throw new
|
|
156
|
-
`Archival timeout (${REQUEST_ARCHIVAL_MAX_ATTEMPTS} attempts) for ${contractId}`,
|
|
157
|
-
);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Return the argument of the first exercise of `choice` on `contractId` in a
|
|
162
|
-
* transaction tree, typed as the caller expects. Generic over choice and arg
|
|
163
|
-
* type; returns undefined when no such exercise is present.
|
|
164
|
-
*/
|
|
165
|
-
export const getExercisedChoiceArgument = <TArg = unknown>(
|
|
166
|
-
transaction: CantonTransaction,
|
|
167
|
-
contractId: string,
|
|
168
|
-
choice: string,
|
|
169
|
-
): TArg | undefined => {
|
|
170
|
-
for (const event of transaction.events ?? []) {
|
|
171
|
-
if (!('ExercisedEvent' in event)) continue;
|
|
172
|
-
const { ExercisedEvent: exercised } = event;
|
|
173
|
-
if (exercised.contractId === contractId && exercised.choice === choice) {
|
|
174
|
-
return exercised.choiceArgument as TArg;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return undefined;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Wait for a Request to be archived and confirm it was accepted, returning the
|
|
182
|
-
* IRequest_Accept choice argument typed as `TAccept` (callers pass the codegen
|
|
183
|
-
* `IRequest_Accept` type to get `acceptContext` typed).
|
|
184
|
-
*
|
|
185
|
-
* IRequest_Accept and IRequest_Reject both end with `archive reqCid` in their
|
|
186
|
-
* Daml impls, so the archive alone can't distinguish them — the choice is read
|
|
187
|
-
* off the archive transaction tree. Throws NonRetryableError on rejection, on
|
|
188
|
-
* archival via any other choice (unexpected), or on timeout.
|
|
189
|
-
*/
|
|
190
|
-
export const waitForRequestArchival = async <TAccept = unknown>(
|
|
191
|
-
client: ICantonClient,
|
|
192
|
-
requestContractId: string,
|
|
193
|
-
parties: string[],
|
|
194
|
-
): Promise<TAccept> => {
|
|
195
|
-
const transaction = await waitForArchival(client, requestContractId, parties);
|
|
196
|
-
|
|
197
|
-
const accept = getExercisedChoiceArgument<TAccept>(
|
|
198
|
-
transaction,
|
|
199
|
-
requestContractId,
|
|
200
|
-
IREQUEST_ACCEPT_CHOICE,
|
|
201
|
-
);
|
|
202
|
-
if (accept !== undefined) return accept;
|
|
203
|
-
|
|
204
|
-
const reject = getExercisedChoiceArgument<{ rejectContext?: unknown }>(
|
|
205
|
-
transaction,
|
|
206
|
-
requestContractId,
|
|
207
|
-
IREQUEST_REJECT_CHOICE,
|
|
208
|
-
);
|
|
209
|
-
if (reject !== undefined) {
|
|
210
|
-
const reason = reject.rejectContext
|
|
211
|
-
? JSON.stringify(reject.rejectContext).slice(0, 500)
|
|
212
|
-
: 'unknown';
|
|
213
|
-
throw new NonRetryableError(`Request rejected — rejectContext: ${reason}`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const consuming = getConsumingChoice(transaction, requestContractId);
|
|
217
|
-
throw new NonRetryableError(
|
|
218
|
-
`Request ${requestContractId} archived via unexpected choice ${consuming ?? '<none>'}`,
|
|
219
|
-
);
|
|
95
|
+
throw new NonEmittableError(`Contract ${contractId} is not archived yet`);
|
|
220
96
|
};
|
|
221
97
|
|
|
222
|
-
/**
|
|
223
|
-
const
|
|
98
|
+
/** Returns the first exercised event on `contractId` in a transaction tree, if any. */
|
|
99
|
+
export const getExercisedEvent = (
|
|
224
100
|
transaction: CantonTransaction,
|
|
225
101
|
contractId: string,
|
|
226
|
-
):
|
|
102
|
+
): ExercisedEvent | undefined => {
|
|
227
103
|
for (const event of transaction.events ?? []) {
|
|
228
104
|
if (!('ExercisedEvent' in event)) continue;
|
|
229
105
|
const { ExercisedEvent: exercised } = event;
|
|
230
|
-
if (exercised.contractId === contractId
|
|
231
|
-
return exercised
|
|
106
|
+
if (exercised.contractId === contractId) {
|
|
107
|
+
return exercised;
|
|
232
108
|
}
|
|
233
109
|
}
|
|
234
110
|
return undefined;
|
package/src/index.ts
CHANGED
package/src/provider.ts
CHANGED
|
@@ -14,66 +14,49 @@ import { Configuration } from './open-api/runtime';
|
|
|
14
14
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Composite provider for Canton — bundles both transport layers and shared auth.
|
|
18
|
-
*
|
|
19
17
|
* - {@link client} — JSON Ledger API (commands, queries, updates)
|
|
20
18
|
* - {@link grpcClient} — gRPC Admin API (topology transactions, namespace queries)
|
|
21
19
|
* - {@link tokenProvider} — Shared Bearer-token source used by both transports
|
|
20
|
+
* - {@link walletUrl} — Canton Network validator wallet (scan-proxy CIP-56 endpoints)
|
|
22
21
|
*/
|
|
23
|
-
export type
|
|
22
|
+
export type CantonChainProvider = {
|
|
24
23
|
client: ICantonClient;
|
|
25
24
|
grpcClient: CantonGrpcClient;
|
|
26
25
|
tokenProvider: TokenProvider;
|
|
27
26
|
/** Validator wallet URL for scan-proxy CIP-56 endpoints. Required. */
|
|
28
27
|
walletUrl: string;
|
|
29
|
-
/** LayerZero sequencer base URL (e.g. "http://localhost:8080"); vApp JSON-RPC at `/vapp`. Required. */
|
|
30
|
-
sequencerUrl: string;
|
|
31
|
-
/** Sequencer validator pubkeys plus quorum for verifying signed read responses. */
|
|
32
|
-
sequencerCommittee?: SequencerCommittee;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Client-side committee config for secp256k1 sequencer read verification.
|
|
37
|
-
* Pubkeys must be hex-encoded with `0x` prefix in the same format returned by
|
|
38
|
-
* `recoverSecp256k1PublicKey`.
|
|
39
|
-
*/
|
|
40
|
-
export type SequencerCommittee = {
|
|
41
|
-
publicKeys: `0x${string}`[];
|
|
42
|
-
quorum: number;
|
|
43
28
|
};
|
|
44
29
|
|
|
45
30
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
31
|
// URI Parsing
|
|
47
32
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
33
|
|
|
49
|
-
export type
|
|
34
|
+
export type CantonChainUriParams = {
|
|
50
35
|
/** Base URL of the JSON Ledger API (e.g. "http://localhost:5975") */
|
|
51
36
|
jsonApiUrl: string;
|
|
52
37
|
/** Host:port of the gRPC Admin API (e.g. "localhost:5076") */
|
|
53
38
|
adminApiHost: string;
|
|
54
39
|
/** Ledger API user ID (default: "participant_admin") */
|
|
55
40
|
userId: string;
|
|
41
|
+
/** Parties granted `actAs` on localnet tokens (comma-separated in the URI). */
|
|
42
|
+
actAs: string[];
|
|
56
43
|
/** Validator wallet URL for scan-proxy CIP-56 endpoints (e.g. "http://localhost:7201"). Required. */
|
|
57
44
|
walletUrl: string;
|
|
58
|
-
/** LayerZero sequencer base URL (e.g. "http://localhost:8080"); vApp JSON-RPC at `/vapp`. Required. */
|
|
59
|
-
sequencerUrl: string;
|
|
60
|
-
/** Sequencer committee config parsed from the provider URI. */
|
|
61
|
-
sequencerCommittee?: SequencerCommittee;
|
|
62
45
|
/** OAuth2 client credentials config — present only for non-local environments */
|
|
63
46
|
oauth2?: ClientCredentialsTokenProviderConfig;
|
|
64
47
|
};
|
|
65
48
|
|
|
66
49
|
/**
|
|
67
|
-
* Parse a Canton provider URI into its constituent parts.
|
|
50
|
+
* Parse a Canton chain provider URI into its constituent parts.
|
|
68
51
|
*
|
|
69
52
|
* URI format:
|
|
70
|
-
* `<json-api-base>?admin-api=<host:port>&user-id=<id>[&token-url=...&client-id=...&client-secret=...&scope=...&audience=...]`
|
|
53
|
+
* `<json-api-base>?admin-api=<host:port>&user-id=<id>&wallet-url=<url>[&token-url=...&client-id=...&client-secret=...&scope=...&audience=...]`
|
|
71
54
|
*
|
|
72
55
|
* The base URL (everything before the query string) is the JSON Ledger API endpoint.
|
|
73
56
|
* Canton-specific params are extracted and removed; any remaining query params are
|
|
74
57
|
* preserved on the JSON API URL.
|
|
75
58
|
*/
|
|
76
|
-
export function
|
|
59
|
+
export function parseCantonChainUri(uri: string): CantonChainUriParams {
|
|
77
60
|
const url = new URL(uri);
|
|
78
61
|
|
|
79
62
|
const adminApiHost = url.searchParams.get('admin-api');
|
|
@@ -82,26 +65,26 @@ export function parseCantonUri(uri: string): CantonUriParams {
|
|
|
82
65
|
}
|
|
83
66
|
|
|
84
67
|
const userId = url.searchParams.get('user-id') ?? 'participant_admin';
|
|
68
|
+
const actAsParam = url.searchParams.get('act-as');
|
|
69
|
+
const actAs =
|
|
70
|
+
actAsParam
|
|
71
|
+
?.split(',')
|
|
72
|
+
.map((party) => party.trim())
|
|
73
|
+
.filter((party) => party.length > 0) ?? [];
|
|
85
74
|
const walletUrl = url.searchParams.get('wallet-url');
|
|
86
75
|
if (!walletUrl) {
|
|
87
76
|
throw new Error('Canton provider URI missing required "wallet-url" query parameter');
|
|
88
77
|
}
|
|
89
|
-
const sequencerUrl = url.searchParams.get('sequencer-url');
|
|
90
|
-
if (!sequencerUrl) {
|
|
91
|
-
throw new Error('Canton provider URI missing required "sequencer-url" query parameter');
|
|
92
|
-
}
|
|
93
|
-
const sequencerValidatorsParam = url.searchParams.get('sequencer-validators');
|
|
94
|
-
const sequencerQuorumParam = url.searchParams.get('sequencer-quorum');
|
|
95
78
|
const tokenUrl = url.searchParams.get('token-url') ?? undefined;
|
|
96
79
|
const clientId = url.searchParams.get('client-id') ?? undefined;
|
|
97
80
|
const clientSecret = url.searchParams.get('client-secret') ?? undefined;
|
|
98
81
|
const scope = url.searchParams.get('scope') ?? undefined;
|
|
99
82
|
const audience = url.searchParams.get('audience') ?? undefined;
|
|
100
83
|
|
|
101
|
-
// Remove Canton params to get clean JSON API URL
|
|
102
84
|
for (const key of [
|
|
103
85
|
'admin-api',
|
|
104
86
|
'user-id',
|
|
87
|
+
'act-as',
|
|
105
88
|
'wallet-url',
|
|
106
89
|
'sequencer-url',
|
|
107
90
|
'sequencer-validators',
|
|
@@ -115,46 +98,6 @@ export function parseCantonUri(uri: string): CantonUriParams {
|
|
|
115
98
|
url.searchParams.delete(key);
|
|
116
99
|
}
|
|
117
100
|
|
|
118
|
-
let sequencerCommittee: SequencerCommittee | undefined;
|
|
119
|
-
if (sequencerValidatorsParam !== null && sequencerQuorumParam !== null) {
|
|
120
|
-
const publicKeys = sequencerValidatorsParam
|
|
121
|
-
.split(',')
|
|
122
|
-
.map((s) => s.trim().toLowerCase())
|
|
123
|
-
.filter((s) => s.length > 0);
|
|
124
|
-
if (publicKeys.length === 0) {
|
|
125
|
-
throw new Error(
|
|
126
|
-
'Canton provider URI sequencer-validators must contain at least one pubkey',
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
for (const publicKey of publicKeys) {
|
|
130
|
-
if (!publicKey.startsWith('0x')) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`Canton provider URI sequencer-validators pubkey must start with 0x, got: ${publicKey.slice(0, 10)}...`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const quorum = Number(sequencerQuorumParam);
|
|
138
|
-
if (!Number.isInteger(quorum) || quorum < 1) {
|
|
139
|
-
throw new Error(
|
|
140
|
-
`Canton provider URI sequencer-quorum must be a positive integer, got: ${sequencerQuorumParam}`,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
if (quorum > publicKeys.length) {
|
|
144
|
-
throw new Error(
|
|
145
|
-
`Canton provider URI sequencer-quorum=${quorum} exceeds committee size ${publicKeys.length}`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
sequencerCommittee = {
|
|
149
|
-
publicKeys: publicKeys as `0x${string}`[],
|
|
150
|
-
quorum,
|
|
151
|
-
};
|
|
152
|
-
} else if (sequencerValidatorsParam !== null || sequencerQuorumParam !== null) {
|
|
153
|
-
throw new Error(
|
|
154
|
-
'Canton provider URI must set both sequencer-validators and sequencer-quorum together',
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
101
|
const jsonApiUrl = url.toString().replace(/\/$/, '');
|
|
159
102
|
|
|
160
103
|
const oauth2 =
|
|
@@ -166,9 +109,8 @@ export function parseCantonUri(uri: string): CantonUriParams {
|
|
|
166
109
|
jsonApiUrl,
|
|
167
110
|
adminApiHost,
|
|
168
111
|
userId,
|
|
112
|
+
actAs,
|
|
169
113
|
walletUrl,
|
|
170
|
-
sequencerUrl,
|
|
171
|
-
sequencerCommittee,
|
|
172
114
|
oauth2,
|
|
173
115
|
};
|
|
174
116
|
}
|
|
@@ -177,7 +119,7 @@ export function parseCantonUri(uri: string): CantonUriParams {
|
|
|
177
119
|
// Provider Factory
|
|
178
120
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
179
121
|
|
|
180
|
-
export type
|
|
122
|
+
export type CreateCantonChainProviderOptions = {
|
|
181
123
|
/** Raw provider URI (from provider config) */
|
|
182
124
|
uri: string;
|
|
183
125
|
/** When true, uses LocalTokenProvider (self-signed JWTs) instead of OAuth2 */
|
|
@@ -185,17 +127,17 @@ export type CreateCantonProviderOptions = {
|
|
|
185
127
|
};
|
|
186
128
|
|
|
187
129
|
/**
|
|
188
|
-
* Create a {@link
|
|
130
|
+
* Create a {@link CantonChainProvider} from a provider URI.
|
|
189
131
|
*
|
|
190
132
|
* - **isLocal=true**: Uses {@link LocalTokenProvider} with admin-level access (self-signed JWTs).
|
|
191
133
|
* - **isLocal=false**: Uses {@link ClientCredentialsTokenProvider} with OAuth2 client
|
|
192
134
|
* credentials extracted from the URI query parameters.
|
|
193
135
|
*/
|
|
194
|
-
export function
|
|
136
|
+
export function createCantonChainProvider({
|
|
195
137
|
uri,
|
|
196
138
|
isLocal,
|
|
197
|
-
}:
|
|
198
|
-
const params =
|
|
139
|
+
}: CreateCantonChainProviderOptions): CantonChainProvider {
|
|
140
|
+
const params = parseCantonChainUri(uri);
|
|
199
141
|
|
|
200
142
|
const tokenProvider = createTokenProvider(params, isLocal);
|
|
201
143
|
|
|
@@ -217,12 +159,10 @@ export function createCantonProvider({
|
|
|
217
159
|
grpcClient,
|
|
218
160
|
tokenProvider,
|
|
219
161
|
walletUrl: params.walletUrl,
|
|
220
|
-
sequencerUrl: params.sequencerUrl,
|
|
221
|
-
sequencerCommittee: params.sequencerCommittee,
|
|
222
162
|
};
|
|
223
163
|
}
|
|
224
164
|
|
|
225
|
-
function createTokenProvider(params:
|
|
165
|
+
function createTokenProvider(params: CantonChainUriParams, isLocal: boolean): TokenProvider {
|
|
226
166
|
if (isLocal) {
|
|
227
167
|
return new LocalTokenProvider({
|
|
228
168
|
userId: params.userId,
|
package/src/sdk/canton-sdk.ts
CHANGED
|
@@ -452,6 +452,19 @@ export class CantonSdk {
|
|
|
452
452
|
return this.#client.queryActiveContractsByParties(parties, offset, templateIds, opts);
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Query active contracts visible to any of the given parties that implement
|
|
457
|
+
* the given interfaces (single API call).
|
|
458
|
+
*/
|
|
459
|
+
queryActiveContractsByInterface(
|
|
460
|
+
parties: string[],
|
|
461
|
+
offset: number,
|
|
462
|
+
interfaceIds: string[],
|
|
463
|
+
opts?: { limit?: number },
|
|
464
|
+
): Promise<JsActiveContract[]> {
|
|
465
|
+
return this.#client.queryActiveContractsByInterface(parties, offset, interfaceIds, opts);
|
|
466
|
+
}
|
|
467
|
+
|
|
455
468
|
/**
|
|
456
469
|
* Query active contracts visible to any of the given parties, deduplicated by contractId.
|
|
457
470
|
* Returns normalized contracts (flat shape). Use when you need a merged view across multiple parties.
|
package/src/transaction.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
assignedTransactionSchema,
|
|
5
|
+
buildTransactionWithResultSchema,
|
|
5
6
|
minedTransactionSchema,
|
|
6
7
|
signedTransactionSchema,
|
|
7
8
|
transactionSchema,
|
|
@@ -71,6 +72,9 @@ export const cantonTransactionSchema = transactionSchema.extend({
|
|
|
71
72
|
}),
|
|
72
73
|
});
|
|
73
74
|
|
|
75
|
+
export const cantonTransactionWithResultSchema =
|
|
76
|
+
buildTransactionWithResultSchema(cantonTransactionSchema);
|
|
77
|
+
|
|
74
78
|
const cantonInflightTransactionSchema = z.object({
|
|
75
79
|
/** The prepared transaction blob */
|
|
76
80
|
preparedTransaction: z.string(),
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
derEncodeSignature,
|
|
5
|
+
publicKeyToSpkiDer,
|
|
6
|
+
SECP256K1_SPKI_DER_HEADER,
|
|
7
|
+
SECP256K1_SPKI_DER_HEADER_HEX,
|
|
8
|
+
} from '../src/crypto';
|
|
9
|
+
|
|
10
|
+
// A throwaway uncompressed secp256k1 public key shape (0x04 ‖ X ‖ Y, 65 bytes).
|
|
11
|
+
const UNCOMPRESSED_KEY = `04${'ab'.repeat(64)}`;
|
|
12
|
+
|
|
13
|
+
describe('SECP256K1_SPKI_DER_HEADER', () => {
|
|
14
|
+
it('is the canonical 23-byte secp256k1 SPKI-DER header', () => {
|
|
15
|
+
expect(SECP256K1_SPKI_DER_HEADER).toHaveLength(23);
|
|
16
|
+
expect(SECP256K1_SPKI_DER_HEADER_HEX).toBe(
|
|
17
|
+
'3056301006072a8648ce3d020106052b8104000a034200',
|
|
18
|
+
);
|
|
19
|
+
expect(SECP256K1_SPKI_DER_HEADER_HEX).toHaveLength(46);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('publicKeyToSpkiDer', () => {
|
|
24
|
+
it('prepends the SPKI-DER header to the uncompressed key body', () => {
|
|
25
|
+
const spki = publicKeyToSpkiDer(`0x${UNCOMPRESSED_KEY}`);
|
|
26
|
+
expect(spki).toBe(SECP256K1_SPKI_DER_HEADER_HEX + UNCOMPRESSED_KEY);
|
|
27
|
+
expect(spki).toHaveLength(46 + 130);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('lowercases and tolerates a missing 0x prefix', () => {
|
|
31
|
+
expect(publicKeyToSpkiDer(UNCOMPRESSED_KEY.toUpperCase())).toBe(
|
|
32
|
+
SECP256K1_SPKI_DER_HEADER_HEX + UNCOMPRESSED_KEY,
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('rejects a non-uncompressed (wrong-length or non-0x04) key', () => {
|
|
37
|
+
expect(() => publicKeyToSpkiDer('0x02abcd')).toThrow();
|
|
38
|
+
expect(() => publicKeyToSpkiDer(`03${'ab'.repeat(64)}`)).toThrow();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('derEncodeSignature', () => {
|
|
43
|
+
it('DER-encodes (r, s) as SEQUENCE { INTEGER r, INTEGER s } with minimal integers', () => {
|
|
44
|
+
// r = 1, s = 2 → 30 06 02 01 01 02 01 02
|
|
45
|
+
expect(derEncodeSignature('01', '02')).toBe('3006020101020102');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('prepends 0x00 to an integer whose high bit is set', () => {
|
|
49
|
+
// r = 0xff (high bit set, needs a leading 0x00), s = 1
|
|
50
|
+
expect(derEncodeSignature('ff', '01')).toBe('3007020200ff020101');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
assertHexLength,
|
|
5
|
+
codePointLength,
|
|
6
|
+
formatDamlDecimal,
|
|
7
|
+
IntLikeSchema,
|
|
8
|
+
intToHex,
|
|
9
|
+
keccak256Hex,
|
|
10
|
+
padLeft,
|
|
11
|
+
showBool,
|
|
12
|
+
showInt,
|
|
13
|
+
textToHex,
|
|
14
|
+
timeToUnixSeconds,
|
|
15
|
+
toBigInt,
|
|
16
|
+
} from '../src/daml-serde';
|
|
17
|
+
|
|
18
|
+
describe('toBigInt', () => {
|
|
19
|
+
it('passes through bigints and converts safe-integer numbers', () => {
|
|
20
|
+
expect(toBigInt(5)).toBe(5n);
|
|
21
|
+
expect(toBigInt(7n)).toBe(7n);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('rejects unsafe-integer numbers', () => {
|
|
25
|
+
expect(() => toBigInt(2 ** 53)).toThrow(/safe integer/);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('IntLikeSchema', () => {
|
|
30
|
+
it('accepts bigints and safe-integer numbers (incl. negatives)', () => {
|
|
31
|
+
expect(IntLikeSchema.parse(5)).toBe(5);
|
|
32
|
+
expect(IntLikeSchema.parse(-5)).toBe(-5);
|
|
33
|
+
expect(IntLikeSchema.parse(2n ** 64n)).toBe(2n ** 64n);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('rejects unsafe-integer numbers (must use a bigint)', () => {
|
|
37
|
+
expect(IntLikeSchema.safeParse(2 ** 53).success).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('textToHex', () => {
|
|
42
|
+
it('encodes ASCII two hex digits per char', () => {
|
|
43
|
+
expect(textToHex('01')).toBe('3031');
|
|
44
|
+
expect(textToHex('setSeed')).toBe('73657453656564');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('rejects non-ASCII code points', () => {
|
|
48
|
+
expect(() => textToHex('café')).toThrow(/Non-ASCII/);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('intToHex', () => {
|
|
53
|
+
it('is minimal and lowercase', () => {
|
|
54
|
+
expect(intToHex(0)).toBe('0');
|
|
55
|
+
expect(intToHex(255)).toBe('ff');
|
|
56
|
+
expect(intToHex(2n ** 63n - 1n)).toBe('7fffffffffffffff');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('rejects negative values', () => {
|
|
60
|
+
expect(() => intToHex(-1)).toThrow(/negative/);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('padLeft', () => {
|
|
65
|
+
it('left-pads with zeros and rejects oversize values', () => {
|
|
66
|
+
expect(padLeft(16, '15180')).toBe('0000000000015180');
|
|
67
|
+
expect(() => padLeft(2, 'abc')).toThrow(/exceeds pad width/);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('assertHexLength', () => {
|
|
72
|
+
it('returns stripped lowercase hex of the exact length', () => {
|
|
73
|
+
expect(assertHexLength(4, '0xABcd')).toBe('abcd');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('rejects wrong length, odd length, and non-hex', () => {
|
|
77
|
+
expect(() => assertHexLength(4, 'abc')).toThrow();
|
|
78
|
+
expect(() => assertHexLength(4, 'abcde')).toThrow();
|
|
79
|
+
expect(() => assertHexLength(4, 'zzzz')).toThrow();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('show helpers', () => {
|
|
84
|
+
it('showBool capitalizes like DAML', () => {
|
|
85
|
+
expect(showBool(true)).toBe('True');
|
|
86
|
+
expect(showBool(false)).toBe('False');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('showInt is base-10', () => {
|
|
90
|
+
expect(showInt(0)).toBe('0');
|
|
91
|
+
expect(showInt(-5)).toBe('-5');
|
|
92
|
+
expect(showInt(123n)).toBe('123');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('formatDamlDecimal', () => {
|
|
97
|
+
it('keeps at least one fractional digit and strips trailing zeros', () => {
|
|
98
|
+
expect(formatDamlDecimal('1.50')).toBe('1.5');
|
|
99
|
+
expect(formatDamlDecimal('100')).toBe('100.0');
|
|
100
|
+
expect(formatDamlDecimal('0')).toBe('0.0');
|
|
101
|
+
expect(formatDamlDecimal('-1.5')).toBe('-1.5');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('rejects more than 10 fractional digits and exponent notation', () => {
|
|
105
|
+
expect(() => formatDamlDecimal('0.123456789012')).toThrow();
|
|
106
|
+
expect(() => formatDamlDecimal(1e-12)).toThrow();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('keccak256Hex', () => {
|
|
111
|
+
it('hashes the bytes denoted by the hex string', () => {
|
|
112
|
+
// keccak256 of the empty byte string.
|
|
113
|
+
expect(keccak256Hex('')).toBe(
|
|
114
|
+
'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470',
|
|
115
|
+
);
|
|
116
|
+
// keccak256 of the single byte 0x00.
|
|
117
|
+
expect(keccak256Hex('00')).toBe(
|
|
118
|
+
'bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a',
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('rejects malformed hex rather than hashing raw text', () => {
|
|
123
|
+
expect(() => keccak256Hex('xyz')).toThrow(/valid hex/);
|
|
124
|
+
expect(() => keccak256Hex('abc')).toThrow(/valid hex/);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('codePointLength', () => {
|
|
129
|
+
it('counts Unicode code points', () => {
|
|
130
|
+
expect(codePointLength('alice')).toBe(5);
|
|
131
|
+
expect(codePointLength('𝔸')).toBe(1);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('timeToUnixSeconds', () => {
|
|
136
|
+
it('returns whole seconds since the unix epoch', () => {
|
|
137
|
+
expect(timeToUnixSeconds(new Date('1970-01-01T00:00:01.000Z'))).toBe(1n);
|
|
138
|
+
expect(timeToUnixSeconds(new Date('2021-01-01T00:00:00.000Z'))).toBe(1609459200n);
|
|
139
|
+
});
|
|
140
|
+
});
|