@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.
Files changed (230) hide show
  1. package/.turbo/turbo-build.log +837 -821
  2. package/.turbo/turbo-test.log +22 -19
  3. package/dist/{PVONQCBG.js → 2UKHG3HM.js} +3 -3
  4. package/dist/{PVONQCBG.js.map → 2UKHG3HM.js.map} +1 -1
  5. package/dist/{GMQEAD3I.js → 34I4OGQK.js} +3 -3
  6. package/dist/{GMQEAD3I.js.map → 34I4OGQK.js.map} +1 -1
  7. package/dist/{NLMU226B.js → 45YQNHS2.js} +3 -3
  8. package/dist/{NLMU226B.js.map → 45YQNHS2.js.map} +1 -1
  9. package/dist/{HNZRSDTR.js → 4CJTJBWF.js} +15 -46
  10. package/dist/4CJTJBWF.js.map +1 -0
  11. package/dist/{KK2NBQSG.js → 4GYCD3G4.js} +3 -3
  12. package/dist/{KK2NBQSG.js.map → 4GYCD3G4.js.map} +1 -1
  13. package/dist/{EIXIZULE.js → 5HH7PZW3.js} +37 -37
  14. package/dist/{EIXIZULE.js.map → 5HH7PZW3.js.map} +1 -1
  15. package/dist/{OQ2FVATH.js → 5OA7RJHH.js} +3 -3
  16. package/dist/{OQ2FVATH.js.map → 5OA7RJHH.js.map} +1 -1
  17. package/dist/{QYQCAXCB.js → 73I7CBP3.js} +3 -3
  18. package/dist/{QYQCAXCB.js.map → 73I7CBP3.js.map} +1 -1
  19. package/dist/{MDEOBKC3.js → 7GI6QGS6.js} +3 -3
  20. package/dist/{MDEOBKC3.js.map → 7GI6QGS6.js.map} +1 -1
  21. package/dist/{IOTRKLGP.js → 7STDATVZ.js} +3 -3
  22. package/dist/{IOTRKLGP.js.map → 7STDATVZ.js.map} +1 -1
  23. package/dist/{T4AL4FJH.js → ALC4M7WK.js} +3 -3
  24. package/dist/{T4AL4FJH.js.map → ALC4M7WK.js.map} +1 -1
  25. package/dist/{TWUSN2QY.js → AMLJTQWB.js} +3 -3
  26. package/dist/{TWUSN2QY.js.map → AMLJTQWB.js.map} +1 -1
  27. package/dist/{YCNGW63C.js → BL22CZFA.js} +3 -3
  28. package/dist/{YCNGW63C.js.map → BL22CZFA.js.map} +1 -1
  29. package/dist/{ZN2CNFMH.js → BLWJ63ZZ.js} +3 -3
  30. package/dist/{ZN2CNFMH.js.map → BLWJ63ZZ.js.map} +1 -1
  31. package/dist/CJ2WHLYC.js +116 -0
  32. package/dist/CJ2WHLYC.js.map +1 -0
  33. package/dist/{4AKFOZFY.js → CLAE73FR.js} +3 -3
  34. package/dist/{4AKFOZFY.js.map → CLAE73FR.js.map} +1 -1
  35. package/dist/CMDFOA7C.js +47 -0
  36. package/dist/CMDFOA7C.js.map +1 -0
  37. package/dist/CXTOAS7N.js +18 -0
  38. package/dist/CXTOAS7N.js.map +1 -0
  39. package/dist/{NX67V54F.js → DAP5XJGH.js} +3 -3
  40. package/dist/{NX67V54F.js.map → DAP5XJGH.js.map} +1 -1
  41. package/dist/{PSB6BQFH.js → EYLCZMM4.js} +5 -4
  42. package/dist/EYLCZMM4.js.map +1 -0
  43. package/dist/FPRRDKT5.js +52 -0
  44. package/dist/FPRRDKT5.js.map +1 -0
  45. package/dist/{KJRFPBY4.js → FXR2Z54G.js} +3 -3
  46. package/dist/{KJRFPBY4.js.map → FXR2Z54G.js.map} +1 -1
  47. package/dist/{MJCI4XZ2.js → HLWLB4AY.js} +3 -3
  48. package/dist/{MJCI4XZ2.js.map → HLWLB4AY.js.map} +1 -1
  49. package/dist/{XSEEW6TB.js → HRZTWWO4.js} +3 -3
  50. package/dist/{XSEEW6TB.js.map → HRZTWWO4.js.map} +1 -1
  51. package/dist/{QHEXOE3Y.js → JI3XNGXO.js} +3 -27
  52. package/dist/JI3XNGXO.js.map +1 -0
  53. package/dist/{EPHLTMJZ.js → JSG7MGHH.js} +3 -3
  54. package/dist/{EPHLTMJZ.js.map → JSG7MGHH.js.map} +1 -1
  55. package/dist/{JH2I4JGJ.js → JXKVPYCE.js} +3 -3
  56. package/dist/{JH2I4JGJ.js.map → JXKVPYCE.js.map} +1 -1
  57. package/dist/{4VLNRPYZ.js → K2SFNQJC.js} +5 -5
  58. package/dist/{4VLNRPYZ.js.map → K2SFNQJC.js.map} +1 -1
  59. package/dist/{H7FW24YI.js → KBZ6YXX4.js} +3 -3
  60. package/dist/{H7FW24YI.js.map → KBZ6YXX4.js.map} +1 -1
  61. package/dist/{ZF6ASBPX.js → KH7RP6Q4.js} +3 -3
  62. package/dist/{ZF6ASBPX.js.map → KH7RP6Q4.js.map} +1 -1
  63. package/dist/{AV2FN74T.js → KTNEUXUI.js} +4 -4
  64. package/dist/{AV2FN74T.js.map → KTNEUXUI.js.map} +1 -1
  65. package/dist/{D3AV6J7R.js → LL7Z56UN.js} +3 -3
  66. package/dist/{D3AV6J7R.js.map → LL7Z56UN.js.map} +1 -1
  67. package/dist/{AYAFFPBV.js → MFVQ6EHM.js} +3 -3
  68. package/dist/{AYAFFPBV.js.map → MFVQ6EHM.js.map} +1 -1
  69. package/dist/{U7ISOK6B.js → NKXEPWZV.js} +3 -3
  70. package/dist/{U7ISOK6B.js.map → NKXEPWZV.js.map} +1 -1
  71. package/dist/{P3A2XIFY.js → NS763D57.js} +3 -3
  72. package/dist/{P3A2XIFY.js.map → NS763D57.js.map} +1 -1
  73. package/dist/{3UD2GTXW.js → PGQCNYFD.js} +3 -3
  74. package/dist/{3UD2GTXW.js.map → PGQCNYFD.js.map} +1 -1
  75. package/dist/{2UWZKS5E.js → Q3RL6UXD.js} +4 -4
  76. package/dist/{2UWZKS5E.js.map → Q3RL6UXD.js.map} +1 -1
  77. package/dist/{5KFMHJZB.js → QYQWHMQM.js} +3 -3
  78. package/dist/{5KFMHJZB.js.map → QYQWHMQM.js.map} +1 -1
  79. package/dist/{BJXKGZ5Y.js → QZJONP6W.js} +3 -3
  80. package/dist/{BJXKGZ5Y.js.map → QZJONP6W.js.map} +1 -1
  81. package/dist/{X66QZBHT.js → SLTAV2JF.js} +9 -2
  82. package/dist/SLTAV2JF.js.map +1 -0
  83. package/dist/{JV2DWUBW.js → SMLSAWCA.js} +48 -4
  84. package/dist/SMLSAWCA.js.map +1 -0
  85. package/dist/{RQXQCK22.js → UBHUOSK3.js} +3 -3
  86. package/dist/{RQXQCK22.js.map → UBHUOSK3.js.map} +1 -1
  87. package/dist/{OMRF3GH2.js → UO7AO4TN.js} +3 -3
  88. package/dist/{OMRF3GH2.js.map → UO7AO4TN.js.map} +1 -1
  89. package/dist/{R26MH4UH.js → UQMKPY2S.js} +3 -3
  90. package/dist/{R26MH4UH.js.map → UQMKPY2S.js.map} +1 -1
  91. package/dist/{HU7X7G5L.js → WSLU3JGO.js} +3 -3
  92. package/dist/{HU7X7G5L.js.map → WSLU3JGO.js.map} +1 -1
  93. package/dist/{WIQFSNER.js → XINHRIWA.js} +3 -3
  94. package/dist/{WIQFSNER.js.map → XINHRIWA.js.map} +1 -1
  95. package/dist/{PHVMGN7T.js → ZGWBZC76.js} +3 -3
  96. package/dist/{PHVMGN7T.js.map → ZGWBZC76.js.map} +1 -1
  97. package/dist/{AE56UC5I.js → ZPOY5WJ2.js} +3 -3
  98. package/dist/{AE56UC5I.js.map → ZPOY5WJ2.js.map} +1 -1
  99. package/dist/amulet.d.ts +7 -0
  100. package/dist/amulet.d.ts.map +1 -0
  101. package/dist/amulet.js +4 -0
  102. package/dist/amulet.js.map +1 -0
  103. package/dist/auth/index.js +1 -1
  104. package/dist/client/canton-client.d.ts +12 -0
  105. package/dist/client/canton-client.d.ts.map +1 -1
  106. package/dist/client/canton-client.js +138 -138
  107. package/dist/client/index.js +140 -139
  108. package/dist/client/interface.d.ts +12 -0
  109. package/dist/client/interface.d.ts.map +1 -1
  110. package/dist/client/utils.d.ts.map +1 -1
  111. package/dist/client/utils.js +2 -1
  112. package/dist/crypto.d.ts +23 -0
  113. package/dist/crypto.d.ts.map +1 -0
  114. package/dist/crypto.js +4 -0
  115. package/dist/crypto.js.map +1 -0
  116. package/dist/daml-serde.d.ts +73 -0
  117. package/dist/daml-serde.d.ts.map +1 -0
  118. package/dist/daml-serde.js +4 -0
  119. package/dist/daml-serde.js.map +1 -0
  120. package/dist/events.d.ts +9 -36
  121. package/dist/events.d.ts.map +1 -1
  122. package/dist/events.js +2 -2
  123. package/dist/index.d.ts +3 -0
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +150 -147
  126. package/dist/open-api/apis/DefaultApi.js +137 -137
  127. package/dist/open-api/apis/index.js +137 -137
  128. package/dist/open-api/index.js +137 -137
  129. package/dist/open-api/models/Command.js +3 -3
  130. package/dist/open-api/models/Command1.js +2 -2
  131. package/dist/open-api/models/Completion.js +3 -3
  132. package/dist/open-api/models/Completion1.js +3 -3
  133. package/dist/open-api/models/CompletionResponse.js +7 -7
  134. package/dist/open-api/models/CompletionResponseOneOf.js +3 -3
  135. package/dist/open-api/models/CompletionStreamResponse.js +7 -7
  136. package/dist/open-api/models/CreateUserRequest.js +10 -10
  137. package/dist/open-api/models/CumulativeFilter.js +2 -2
  138. package/dist/open-api/models/DeduplicationPeriod.js +1 -1
  139. package/dist/open-api/models/DeduplicationPeriod1.js +1 -1
  140. package/dist/open-api/models/DeduplicationPeriod2.js +3 -3
  141. package/dist/open-api/models/EventFormat.js +2 -2
  142. package/dist/open-api/models/FeaturesDescriptor.js +2 -2
  143. package/dist/open-api/models/Filters.js +2 -2
  144. package/dist/open-api/models/GetActiveContractsRequest.js +2 -2
  145. package/dist/open-api/models/GetEventsByContractIdRequest.js +2 -2
  146. package/dist/open-api/models/GetLedgerApiVersionResponse.js +3 -3
  147. package/dist/open-api/models/GetTransactionByIdRequest.js +2 -2
  148. package/dist/open-api/models/GetTransactionByOffsetRequest.js +2 -2
  149. package/dist/open-api/models/GetUpdateByIdRequest.js +2 -2
  150. package/dist/open-api/models/GetUpdateByOffsetRequest.js +2 -2
  151. package/dist/open-api/models/GetUpdatesRequest.js +2 -2
  152. package/dist/open-api/models/GrantUserRightsRequest.js +10 -10
  153. package/dist/open-api/models/GrantUserRightsResponse.js +10 -10
  154. package/dist/open-api/models/IdentifierFilter.js +2 -2
  155. package/dist/open-api/models/JsCommands.js +5 -5
  156. package/dist/open-api/models/JsContractEntry.js +3 -3
  157. package/dist/open-api/models/JsExecuteSubmissionAndWaitForTransactionRequest.js +7 -7
  158. package/dist/open-api/models/JsExecuteSubmissionAndWaitRequest.js +5 -5
  159. package/dist/open-api/models/JsExecuteSubmissionRequest.js +5 -5
  160. package/dist/open-api/models/JsGetActiveContractsResponse.js +4 -4
  161. package/dist/open-api/models/JsGetUpdateResponse.js +13 -13
  162. package/dist/open-api/models/JsGetUpdateTreesResponse.js +5 -5
  163. package/dist/open-api/models/JsGetUpdatesResponse.js +13 -13
  164. package/dist/open-api/models/JsPrepareSubmissionRequest.js +5 -5
  165. package/dist/open-api/models/JsReassignment.js +1 -1
  166. package/dist/open-api/models/JsReassignmentEvent.js +1 -1
  167. package/dist/open-api/models/JsSubmitAndWaitForReassignmentResponse.js +1 -1
  168. package/dist/open-api/models/JsSubmitAndWaitForTransactionRequest.js +9 -9
  169. package/dist/open-api/models/JsTopologyTransaction.js +6 -6
  170. package/dist/open-api/models/Kind.js +8 -8
  171. package/dist/open-api/models/ListUserRightsResponse.js +10 -10
  172. package/dist/open-api/models/Operation.js +4 -4
  173. package/dist/open-api/models/Reassignment.js +1 -1
  174. package/dist/open-api/models/Reassignment1.js +1 -1
  175. package/dist/open-api/models/ReassignmentCommand.js +3 -3
  176. package/dist/open-api/models/ReassignmentCommands.js +4 -4
  177. package/dist/open-api/models/RevokeUserRightsRequest.js +10 -10
  178. package/dist/open-api/models/RevokeUserRightsResponse.js +10 -10
  179. package/dist/open-api/models/Right.js +9 -9
  180. package/dist/open-api/models/SubmitAndWaitForReassignmentRequest.js +7 -7
  181. package/dist/open-api/models/SubmitReassignmentRequest.js +5 -5
  182. package/dist/open-api/models/TopologyEvent.js +5 -5
  183. package/dist/open-api/models/TopologyEventEvent.js +4 -4
  184. package/dist/open-api/models/TopologyTransaction.js +7 -7
  185. package/dist/open-api/models/TransactionFilter.js +2 -2
  186. package/dist/open-api/models/TransactionFormat.js +2 -2
  187. package/dist/open-api/models/Update.js +12 -12
  188. package/dist/open-api/models/Update1.js +4 -4
  189. package/dist/open-api/models/Update1OneOf1.js +1 -1
  190. package/dist/open-api/models/UpdateFormat.js +2 -2
  191. package/dist/open-api/models/UpdateOneOf1.js +1 -1
  192. package/dist/open-api/models/UpdateOneOf2.js +8 -8
  193. package/dist/open-api/models/UpdateVettedPackagesRequest.js +6 -6
  194. package/dist/open-api/models/VettedPackagesChange.js +5 -5
  195. package/dist/open-api/models/index.js +136 -136
  196. package/dist/provider.d.ts +11 -27
  197. package/dist/provider.d.ts.map +1 -1
  198. package/dist/provider.js +140 -140
  199. package/dist/sdk/canton-sdk.d.ts +7 -0
  200. package/dist/sdk/canton-sdk.d.ts.map +1 -1
  201. package/dist/sdk/canton-sdk.js +1 -1
  202. package/dist/sdk/index.js +1 -1
  203. package/dist/transaction.d.ts +64 -6
  204. package/dist/transaction.d.ts.map +1 -1
  205. package/dist/transaction.js +1 -1
  206. package/package.json +13 -10
  207. package/src/amulet.ts +28 -0
  208. package/src/client/canton-client.ts +60 -1
  209. package/src/client/interface.ts +16 -0
  210. package/src/client/utils.ts +1 -6
  211. package/src/crypto.ts +42 -0
  212. package/src/daml-serde.ts +182 -0
  213. package/src/events.ts +31 -155
  214. package/src/index.ts +3 -0
  215. package/src/provider.ts +22 -82
  216. package/src/sdk/canton-sdk.ts +13 -0
  217. package/src/transaction.ts +4 -0
  218. package/test/crypto.test.ts +52 -0
  219. package/test/daml-serde.test.ts +140 -0
  220. package/test/mocks/scripts/capture-disclosure.ts +5 -2
  221. package/test/mocks/scripts/capture-external-requests.ts +5 -2
  222. package/test/mocks/scripts/capture-unhappy-paths.ts +5 -2
  223. package/test/provider.test.ts +47 -36
  224. package/dist/GXJEEYPP.js +0 -104
  225. package/dist/GXJEEYPP.js.map +0 -1
  226. package/dist/HNZRSDTR.js.map +0 -1
  227. package/dist/JV2DWUBW.js.map +0 -1
  228. package/dist/PSB6BQFH.js.map +0 -1
  229. package/dist/QHEXOE3Y.js.map +0 -1
  230. package/dist/X66QZBHT.js.map +0 -1
package/src/events.ts CHANGED
@@ -1,37 +1,8 @@
1
- import { buildTransactionWithResultSchema } from '@layerzerolabs/common-chain-model';
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 { cantonTransactionSchema } from './transaction';
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 = cantonTxWithResultSchema.safeParse(tx);
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
- * Success check for register-style deploys (an exercise on Uln302 that creates a
97
- * Request, which the validator then accepts or rejects in a later transaction).
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
- for (let attempt = 1; attempt <= REQUEST_ARCHIVAL_MAX_ATTEMPTS; attempt++) {
146
- const { archived } = await client.fetchEventsByContractId(contractId, parties);
147
- if (archived) {
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 NonRetryableError(
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
- /** Name of the consuming exercise on `contractId` in a transaction tree, if any. */
223
- const getConsumingChoice = (
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
- ): string | undefined => {
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 && exercised.consuming) {
231
- return exercised.choice;
106
+ if (exercised.contractId === contractId) {
107
+ return exercised;
232
108
  }
233
109
  }
234
110
  return undefined;
package/src/index.ts CHANGED
@@ -1,4 +1,7 @@
1
+ export * from './amulet';
1
2
  export * from './auth';
3
+ export * from './crypto';
4
+ export * from './daml-serde';
2
5
  export * from './deferredValue';
3
6
  export * from './events';
4
7
  export * from './provider';
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 CantonProvider = {
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 CantonUriParams = {
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 parseCantonUri(uri: string): CantonUriParams {
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 CreateCantonProviderOptions = {
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 CantonProvider} from a provider URI.
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 createCantonProvider({
136
+ export function createCantonChainProvider({
195
137
  uri,
196
138
  isLocal,
197
- }: CreateCantonProviderOptions): CantonProvider {
198
- const params = parseCantonUri(uri);
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: CantonUriParams, isLocal: boolean): TokenProvider {
165
+ function createTokenProvider(params: CantonChainUriParams, isLocal: boolean): TokenProvider {
226
166
  if (isLocal) {
227
167
  return new LocalTokenProvider({
228
168
  userId: params.userId,
@@ -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.
@@ -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
+ });