@cef-ebsi/cli 1.3.2 → 1.5.0

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 (202) hide show
  1. package/bin/cli.js +1 -1
  2. package/dist/abi/pilot/DidRegistry.js +568 -0
  3. package/dist/abi/pilot/DidRegistry.js.map +1 -0
  4. package/dist/abi/{test/TirV4.js → pilot/Ptr.js} +255 -299
  5. package/dist/abi/pilot/Ptr.js.map +1 -0
  6. package/dist/abi/pilot/SchemaSCRegistry.js +264 -0
  7. package/dist/abi/pilot/SchemaSCRegistry.js.map +1 -0
  8. package/dist/abi/{test/TprV3.js → pilot/Tcr.js} +269 -261
  9. package/dist/abi/pilot/Tcr.js.map +1 -0
  10. package/dist/abi/pilot/Timestamp.js +658 -0
  11. package/dist/abi/pilot/Timestamp.js.map +1 -0
  12. package/dist/abi/pilot/Tir.js +428 -0
  13. package/dist/abi/pilot/Tir.js.map +1 -0
  14. package/dist/abi/pilot/TnT.js +842 -0
  15. package/dist/abi/pilot/TnT.js.map +1 -0
  16. package/dist/abi/pilot/Tpr.js +492 -0
  17. package/dist/abi/pilot/Tpr.js.map +1 -0
  18. package/dist/abi/test/DidRegistry.js +144 -658
  19. package/dist/abi/test/DidRegistry.js.map +1 -1
  20. package/dist/abi/{pilot/TirV4.js → test/Ptr.js} +255 -299
  21. package/dist/abi/test/Ptr.js.map +1 -0
  22. package/dist/abi/test/SchemaSCRegistry.js +122 -795
  23. package/dist/abi/test/SchemaSCRegistry.js.map +1 -1
  24. package/dist/abi/{pilot/TprV3.js → test/Tcr.js} +269 -261
  25. package/dist/abi/test/Tcr.js.map +1 -0
  26. package/dist/abi/test/Timestamp.js +239 -673
  27. package/dist/abi/test/Timestamp.js.map +1 -1
  28. package/dist/abi/test/Tir.js +167 -636
  29. package/dist/abi/test/Tir.js.map +1 -1
  30. package/dist/abi/test/TnT.js +842 -0
  31. package/dist/abi/test/TnT.js.map +1 -0
  32. package/dist/abi/test/Tpr.js +166 -942
  33. package/dist/abi/test/Tpr.js.map +1 -1
  34. package/dist/app.js +447 -443
  35. package/dist/app.js.map +1 -1
  36. package/dist/buildParam/{didV3.js → didr.js} +210 -204
  37. package/dist/buildParam/didr.js.map +1 -0
  38. package/dist/buildParam/index.js +42 -48
  39. package/dist/buildParam/index.js.map +1 -1
  40. package/dist/buildParam/ptr.js +80 -0
  41. package/dist/buildParam/ptr.js.map +1 -0
  42. package/dist/buildParam/tcr.js +36 -0
  43. package/dist/buildParam/tcr.js.map +1 -0
  44. package/dist/buildParam/{timestampV3.js → timestamp.js} +179 -136
  45. package/dist/buildParam/timestamp.js.map +1 -0
  46. package/dist/buildParam/{tirV3.js → tir.js} +67 -59
  47. package/dist/buildParam/tir.js.map +1 -0
  48. package/dist/buildParam/{tntV1.js → tnt.js} +90 -61
  49. package/dist/buildParam/tnt.js.map +1 -0
  50. package/dist/buildParam/{tprV2.js → tpr.js} +44 -33
  51. package/dist/buildParam/tpr.js.map +1 -0
  52. package/dist/buildParam/{tsrV2.js → tsr.js} +45 -40
  53. package/dist/buildParam/tsr.js.map +1 -0
  54. package/dist/cli.js +1 -0
  55. package/dist/cli.js.map +1 -1
  56. package/dist/commands/{authorisation-v5.js → authorisation.js} +95 -72
  57. package/dist/commands/authorisation.js.map +1 -0
  58. package/dist/commands/compute.js +429 -389
  59. package/dist/commands/compute.js.map +1 -1
  60. package/dist/commands/{conformance-v3.js → conformance.js} +236 -248
  61. package/dist/commands/conformance.js.map +1 -0
  62. package/dist/commands/hardwarewallet.js +5 -5
  63. package/dist/commands/hardwarewallet.js.map +1 -1
  64. package/dist/commands/index.js +4 -4
  65. package/dist/commands/index.js.map +1 -1
  66. package/dist/commands/{ledger-v4.js → ledger.js} +203 -127
  67. package/dist/commands/ledger.js.map +1 -0
  68. package/dist/commands/ptr.js +56 -0
  69. package/dist/commands/ptr.js.map +1 -0
  70. package/dist/commands/tcr.js +51 -0
  71. package/dist/commands/tcr.js.map +1 -0
  72. package/dist/commands/tnl.js +10 -9
  73. package/dist/commands/tnl.js.map +1 -1
  74. package/dist/commands/tsr.js +46 -0
  75. package/dist/commands/tsr.js.map +1 -0
  76. package/dist/commands/view.js +8 -8
  77. package/dist/commands/view.js.map +1 -1
  78. package/dist/config.js +311 -279
  79. package/dist/config.js.map +1 -1
  80. package/dist/index.js +3 -3
  81. package/dist/index.js.map +1 -1
  82. package/dist/interfaces/index.js +1 -1
  83. package/dist/interfaces/index.js.map +1 -1
  84. package/dist/programs/migrateDid.js +33 -33
  85. package/dist/programs/migrateDid.js.map +1 -1
  86. package/dist/programs/migrateDids.js +39 -42
  87. package/dist/programs/migrateDids.js.map +1 -1
  88. package/dist/programs/migrateTsr.js +153 -113
  89. package/dist/programs/migrateTsr.js.map +1 -1
  90. package/dist/scripts/accreditAndAuthorize/conformance/step1 +1 -1
  91. package/dist/scripts/accreditAndAuthorize/test/step1 +1 -1
  92. package/dist/scripts/accreditTI +1 -1
  93. package/dist/scripts/assets/CTRevocableCredential.json +1 -1
  94. package/dist/scripts/assets/CredentialToAttestVerifiableAuthorisationForTrustChain.json +2 -2
  95. package/dist/scripts/assets/TrustedNodesList.json +1 -1
  96. package/dist/scripts/assets/VerifiableAccreditationToAccredit.json +2 -2
  97. package/dist/scripts/assets/VerifiableAccreditationToAttest.json +2 -2
  98. package/dist/scripts/assets/VerifiableAuthorisationForTrustChain.json +1 -1
  99. package/dist/scripts/assets/VerifiableAuthorisationToInvoke.json +24 -0
  100. package/dist/scripts/assets/VerifiableAuthorisationToOnboard.json +1 -1
  101. package/dist/scripts/bootstrap/1-populateTPR +18 -15
  102. package/dist/scripts/bootstrap/6-setupConformanceIssuer +1 -1
  103. package/dist/scripts/issueVcInvoke +10 -0
  104. package/dist/scripts/issueVcOnboard +1 -1
  105. package/dist/scripts/issueVcRootTAO +1 -1
  106. package/dist/scripts/issueVcTAO +2 -2
  107. package/dist/scripts/issueVcTI +2 -2
  108. package/dist/scripts/issueVcTnl +1 -1
  109. package/dist/scripts/issue_CTRevocableCredential +1 -1
  110. package/dist/scripts/issue_SelfAttestationSupportOffice +2 -2
  111. package/dist/scripts/issue_VerifiableAccreditationToAccredit +2 -2
  112. package/dist/scripts/issue_VerifiableAccreditationToAttest +2 -2
  113. package/dist/scripts/issue_VerifiableAuthorisationForTrustChain +1 -1
  114. package/dist/scripts/issue_VerifiableAuthorisationToOnboard +1 -1
  115. package/dist/scripts/updateVcRootTAO +1 -1
  116. package/dist/scripts/updateVcTAO +2 -2
  117. package/dist/scripts/updateVcTI +2 -2
  118. package/dist/scripts/{wctv3 → wct}/accreditAndAuthorize +1 -1
  119. package/dist/tsconfig.build.tsbuildinfo +1 -0
  120. package/dist/utils/Client.js +81 -68
  121. package/dist/utils/Client.js.map +1 -1
  122. package/dist/utils/HardwareWallet.js +169 -165
  123. package/dist/utils/HardwareWallet.js.map +1 -1
  124. package/dist/utils/http.js +24 -27
  125. package/dist/utils/http.js.map +1 -1
  126. package/dist/utils/index.js +3 -3
  127. package/dist/utils/index.js.map +1 -1
  128. package/dist/utils/jsonrpc.js +14 -14
  129. package/dist/utils/jsonrpc.js.map +1 -1
  130. package/dist/utils/print.js +19 -19
  131. package/dist/utils/print.js.map +1 -1
  132. package/dist/utils/utils.js +180 -173
  133. package/dist/utils/utils.js.map +1 -1
  134. package/package.json +70 -46
  135. package/dist/abi/pilot/DidRegistryV3.js +0 -1082
  136. package/dist/abi/pilot/DidRegistryV3.js.map +0 -1
  137. package/dist/abi/pilot/DidRegistryV4.js +0 -942
  138. package/dist/abi/pilot/DidRegistryV4.js.map +0 -1
  139. package/dist/abi/pilot/SchemaSCRegistryV2.js +0 -474
  140. package/dist/abi/pilot/SchemaSCRegistryV2.js.map +0 -1
  141. package/dist/abi/pilot/TimestampV2.js +0 -1128
  142. package/dist/abi/pilot/TimestampV2.js.map +0 -1
  143. package/dist/abi/pilot/TimestampV3.js +0 -995
  144. package/dist/abi/pilot/TimestampV3.js.map +0 -1
  145. package/dist/abi/pilot/TirV3.js +0 -496
  146. package/dist/abi/pilot/TirV3.js.map +0 -1
  147. package/dist/abi/pilot/TirV4.js.map +0 -1
  148. package/dist/abi/pilot/TnTV1.js +0 -1375
  149. package/dist/abi/pilot/TnTV1.js.map +0 -1
  150. package/dist/abi/pilot/TprV2.js +0 -889
  151. package/dist/abi/pilot/TprV2.js.map +0 -1
  152. package/dist/abi/pilot/TprV3.js.map +0 -1
  153. package/dist/abi/pilot/TsrV3.js +0 -571
  154. package/dist/abi/pilot/TsrV3.js.map +0 -1
  155. package/dist/abi/test/DidRegistryV3.js +0 -1089
  156. package/dist/abi/test/DidRegistryV3.js.map +0 -1
  157. package/dist/abi/test/DidRegistryV4.js +0 -960
  158. package/dist/abi/test/DidRegistryV4.js.map +0 -1
  159. package/dist/abi/test/SchemaSCRegistryV2.js +0 -474
  160. package/dist/abi/test/SchemaSCRegistryV2.js.map +0 -1
  161. package/dist/abi/test/TimestampV2.js +0 -1128
  162. package/dist/abi/test/TimestampV2.js.map +0 -1
  163. package/dist/abi/test/TimestampV3.js +0 -995
  164. package/dist/abi/test/TimestampV3.js.map +0 -1
  165. package/dist/abi/test/TirV3.js +0 -496
  166. package/dist/abi/test/TirV3.js.map +0 -1
  167. package/dist/abi/test/TirV4.js.map +0 -1
  168. package/dist/abi/test/TnTV1.js +0 -1375
  169. package/dist/abi/test/TnTV1.js.map +0 -1
  170. package/dist/abi/test/TprV2.js +0 -889
  171. package/dist/abi/test/TprV2.js.map +0 -1
  172. package/dist/abi/test/TprV3.js.map +0 -1
  173. package/dist/abi/test/TsrV3.js +0 -571
  174. package/dist/abi/test/TsrV3.js.map +0 -1
  175. package/dist/buildParam/didV3.js.map +0 -1
  176. package/dist/buildParam/didV4.js +0 -326
  177. package/dist/buildParam/didV4.js.map +0 -1
  178. package/dist/buildParam/timestampV2.js +0 -317
  179. package/dist/buildParam/timestampV2.js.map +0 -1
  180. package/dist/buildParam/timestampV3.js.map +0 -1
  181. package/dist/buildParam/tirV3.js.map +0 -1
  182. package/dist/buildParam/tirV4.js +0 -119
  183. package/dist/buildParam/tirV4.js.map +0 -1
  184. package/dist/buildParam/tntV1.js.map +0 -1
  185. package/dist/buildParam/tntV2.js +0 -185
  186. package/dist/buildParam/tntV2.js.map +0 -1
  187. package/dist/buildParam/tprV2.js.map +0 -1
  188. package/dist/buildParam/tprV3.js +0 -82
  189. package/dist/buildParam/tprV3.js.map +0 -1
  190. package/dist/buildParam/tsrV2.js.map +0 -1
  191. package/dist/buildParam/tsrV3.js +0 -110
  192. package/dist/buildParam/tsrV3.js.map +0 -1
  193. package/dist/commands/authorisation-v4.js +0 -119
  194. package/dist/commands/authorisation-v4.js.map +0 -1
  195. package/dist/commands/authorisation-v5.js.map +0 -1
  196. package/dist/commands/conformance-v3.js.map +0 -1
  197. package/dist/commands/ledger-v4.js.map +0 -1
  198. package/dist/tsconfig.tsbuildinfo +0 -1
  199. /package/dist/scripts/{wctv3 → wct}/holderWallet +0 -0
  200. /package/dist/scripts/{wctv3 → wct}/issueToHolder +0 -0
  201. /package/dist/scripts/{wctv3 → wct}/pda1 +0 -0
  202. /package/dist/scripts/{wctv3 → wct}/verifier +0 -0
package/dist/app.js CHANGED
@@ -1,18 +1,17 @@
1
- /* eslint-disable no-use-before-define, @typescript-eslint/no-use-before-define */
2
- import { URL } from "node:url";
3
- import { ethers } from "ethers";
4
1
  import { EbsiWallet } from "@cef-ebsi/wallet-lib";
5
- import lodashSet from "lodash.set";
6
- import { calculateJwkThumbprint } from "jose";
7
- import fs from "fs";
8
- import path from "path";
2
+ import { ethers } from "ethers";
9
3
  import Joi from "joi";
10
- import { loadConfig, } from "./config.js";
11
- import * as utils from "./utils/index.js";
4
+ import { calculateJwkThumbprint } from "jose";
5
+ import lodashSet from "lodash.set";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { URL } from "node:url";
12
9
  import { buildParam } from "./buildParam/index.js";
13
- import { jsonrpcBody, paramSignedTransaction, parseLine, prefixWith0x, HardwareWallet, } from "./utils/index.js";
10
+ import { authorisation, compute, conformance, hardwarewallet, ledger, tnl, view, waitToBeMined, } from "./commands/index.js";
11
+ import { loadConfig, } from "./config.js";
14
12
  import { Client } from "./utils/Client.js";
15
- import { authorisationV4, compute, view, conformanceV3, ledgerV4, waitToBeMined, hardwarewallet, tnl, } from "./commands/index.js";
13
+ import * as utils from "./utils/index.js";
14
+ import { HardwareWallet, jsonrpcBody, paramSignedTransaction, parseLine, prefixWith0x, } from "./utils/index.js";
16
15
  let config = loadConfig();
17
16
  let client = new Client();
18
17
  let transactionInfo;
@@ -24,16 +23,198 @@ const rtVars = {}; // runtime variables
24
23
  const algSchema = Joi.string()
25
24
  .valid("ES256K", "ES256", "RS256", "EdDSA")
26
25
  .required();
27
- async function setVar(key, value, printVar = true) {
28
- lodashSet(rtVars, key, value);
29
- if (key.startsWith("user.")) {
30
- await execCommand("using user user");
31
- return;
26
+ export async function execCommand(command, printCommand = false) {
27
+ if (printCommand)
28
+ console.log(`==> ${command}`);
29
+ const parts = parseLine(command);
30
+ let varName = "";
31
+ if (parts.length === 0)
32
+ return 0;
33
+ if (typeof parts[0] === "string" && parts[0].includes(":")) {
34
+ varName = parts[0].replace(":", "");
35
+ parts.shift();
36
+ }
37
+ const [part0, part1] = parts;
38
+ const parts2 = parts.slice(2);
39
+ const method = part0;
40
+ const word1 = part1;
41
+ Joi.assert(method, Joi.string());
42
+ // APIs linked to smart contracts
43
+ if (["did", "tcr", "timestamp", "tir", "tnt", "tpr", "tsr"].includes(method)) {
44
+ Joi.assert(word1, Joi.string());
45
+ const response = await smartContractApi(method, word1, ...parts2);
46
+ if (varName)
47
+ await setVar(varName, response);
48
+ return response;
49
+ }
50
+ const schemaStrings = Joi.array().items(Joi.string());
51
+ const context = {
52
+ client,
53
+ config,
54
+ httpOpts,
55
+ rtVars,
56
+ token,
57
+ transactionInfo,
58
+ };
59
+ // Other APIs and configurations
60
+ switch (method) {
61
+ case "authorisation": {
62
+ Joi.assert(word1, Joi.string());
63
+ Joi.assert(parts2, schemaStrings);
64
+ const inputs = parts2.map((p) => readValue(p));
65
+ const response = await authorisation(word1, inputs, context);
66
+ if (varName)
67
+ await setVar(varName, response);
68
+ return response;
69
+ }
70
+ case "compute": {
71
+ Joi.assert(word1, Joi.string());
72
+ const inputs = parts2.map((p) => readValue(p));
73
+ const response = await compute(word1, inputs, context);
74
+ if (varName)
75
+ await setVar(varName, response);
76
+ return response;
77
+ }
78
+ case "conformance": {
79
+ Joi.assert(word1, Joi.string());
80
+ const inputs = parts2.map((p) => readValue(p));
81
+ const response = await conformance(word1, inputs, context);
82
+ if (varName)
83
+ await setVar(varName, response);
84
+ return response;
85
+ }
86
+ case "domain": {
87
+ Joi.assert(word1, Joi.string());
88
+ await setDomain(word1);
89
+ return 0;
90
+ }
91
+ case "env": {
92
+ Joi.assert(word1, Joi.string().optional());
93
+ const input = readValue(word1);
94
+ await environment(input);
95
+ return 0;
96
+ }
97
+ case "exit": {
98
+ return process.exit(0);
99
+ }
100
+ case "export": {
101
+ let data = readValue(word1);
102
+ const filename = readValue(parts2[0]);
103
+ if (typeof data === "object") {
104
+ data = JSON.stringify(data, null, 2);
105
+ }
106
+ const absolutePath = path.isAbsolute(filename)
107
+ ? filename
108
+ : path.join(`${process.cwd()}`, filename);
109
+ fs.writeFileSync(filename, data);
110
+ utils.yellow(`file saved at ${absolutePath}`);
111
+ return undefined;
112
+ }
113
+ case "fs": {
114
+ Joi.assert(word1, Joi.string());
115
+ const response = fileSystem(word1, ...parts2);
116
+ console.log(response);
117
+ if (varName)
118
+ await setVar(varName, response);
119
+ return response;
120
+ }
121
+ case "get": {
122
+ Joi.assert(word1, Joi.string());
123
+ const input = readValue(word1);
124
+ const response = await utils.httpCall.get(input, context.httpOpts);
125
+ if (varName)
126
+ await setVar(varName, response.data);
127
+ return response.data;
128
+ }
129
+ case "hardwarewallet":
130
+ case "hw": {
131
+ Joi.assert(word1, Joi.string());
132
+ const inputs = parts2.map((p) => {
133
+ if (word1 === "setpin")
134
+ return p;
135
+ return readValue(p);
136
+ });
137
+ const response = await hardwarewallet(word1, inputs, context);
138
+ if (varName)
139
+ await setVar(varName, response);
140
+ return response;
141
+ }
142
+ case "ledger": {
143
+ Joi.assert(word1, Joi.string());
144
+ const inputs = parts2.map((p) => readValue(p));
145
+ const response = await ledger(word1, inputs, false, context);
146
+ if (varName)
147
+ await setVar(varName, response);
148
+ return response;
149
+ }
150
+ case "load": {
151
+ const filename = readValue(word1);
152
+ const data = readFile(filename);
153
+ let response;
154
+ try {
155
+ response = JSON.parse(data);
156
+ }
157
+ catch {
158
+ response = data;
159
+ }
160
+ if (varName)
161
+ await setVar(varName, response);
162
+ return response;
163
+ }
164
+ case "proxyledger": {
165
+ Joi.assert(word1, Joi.string());
166
+ const inputs = parts2.map((p) => readValue(p));
167
+ const response = await ledger(word1, inputs, true, context);
168
+ if (varName)
169
+ await setVar(varName, response);
170
+ return response;
171
+ }
172
+ case "run": {
173
+ Joi.assert(word1, Joi.string());
174
+ const inputs = parts2.map((p) => readValue(p));
175
+ const response = await run(word1, inputs);
176
+ if (varName)
177
+ await setVar(varName, response);
178
+ return response;
179
+ }
180
+ case "set": {
181
+ Joi.assert(word1, Joi.string());
182
+ const inputs = parts2.map((p) => readValue(p));
183
+ let value = inputs[0];
184
+ if (inputs.length > 1) {
185
+ Joi.assert(inputs, schemaStrings);
186
+ value = inputs.join("");
187
+ }
188
+ await setVar(word1, value);
189
+ return value;
190
+ }
191
+ case "tnl": {
192
+ Joi.assert(word1, Joi.string());
193
+ const inputs = parts2.map((p) => readValue(p));
194
+ const response = await tnl(word1, inputs, context);
195
+ if (varName)
196
+ await setVar(varName, response);
197
+ return response;
198
+ }
199
+ case "using": {
200
+ Joi.assert(word1, Joi.string());
201
+ const user = await using(word1, ...parts2);
202
+ return user;
203
+ }
204
+ case "version": {
205
+ Joi.assert(word1, Joi.string().optional());
206
+ const input = readValue(word1);
207
+ await setVersionApis(input);
208
+ return 0;
209
+ }
210
+ case "view": {
211
+ Joi.assert(word1, Joi.string());
212
+ return view([readValue(word1)], context);
213
+ }
214
+ default: {
215
+ throw new Error(`Invalid method '${method}'`);
216
+ }
32
217
  }
33
- if (!printVar)
34
- return;
35
- utils.cyanBold(`Value saved in '${key}':`);
36
- utils.cyan(value);
37
218
  }
38
219
  export async function initConfigVars() {
39
220
  await setVar("domain", config.domain, false);
@@ -47,41 +228,249 @@ export async function initConfigVars() {
47
228
  await setVar("ledgerUrl", config.api.ledger.url, false);
48
229
  await setVar("authorisationUrl", config.api.authorisation.url, false);
49
230
  }
50
- function updateHttpOpts() {
51
- const conformanceId = readValue("conformanceId", true);
52
- httpOpts = {
53
- headers: {
54
- ...(token && {
55
- authorization: `Bearer ${token}`,
56
- }),
57
- ...(conformanceId && { conformance: conformanceId }),
58
- },
59
- };
231
+ export async function main() {
232
+ await initConfigVars();
233
+ while (true) {
234
+ const command = await utils.askQuestion("==> ");
235
+ try {
236
+ await execCommand(command);
237
+ }
238
+ catch (error) {
239
+ utils.red(error.stack);
240
+ }
241
+ }
60
242
  }
61
- function readValue(input, required = false) {
62
- if (!input || typeof input !== "string")
63
- return input;
64
- const parts = input.split(".");
65
- let fieldName = parts[0];
66
- if (typeof rtVars[fieldName] === "undefined") {
67
- if (required)
68
- return undefined;
69
- return input;
243
+ async function environment(env) {
244
+ if (env) {
245
+ config = loadConfig(env, config.versionApis);
246
+ await initConfigVars();
247
+ utils.yellow(`Environment ${env} loaded`);
70
248
  }
71
- let i = 0;
72
- let obj = rtVars;
73
- while (i < parts.length - 1) {
74
- obj = obj[fieldName];
75
- i += 1;
76
- fieldName = parts[i];
249
+ else {
250
+ utils.yellow(`Current environment: ${config.env}`);
77
251
  }
78
- return obj[fieldName];
252
+ const { api, contractAddresses, domain, env: envConfig } = config;
253
+ utils.yellow({
254
+ contractAddresses,
255
+ domain,
256
+ env: envConfig,
257
+ urls: Object.keys(api).map((name) => api[name].url),
258
+ version: config.versionApis,
259
+ });
79
260
  }
80
- function urlPath(inputs) {
261
+ function fileSystem(method, ...params) {
262
+ switch (method) {
263
+ case "readBinaryFile": {
264
+ const [filename] = params;
265
+ Joi.assert(filename, Joi.string());
266
+ return fs.readFileSync(filename);
267
+ }
268
+ case "writeBinaryFile": {
269
+ const filename = readValue(params[0]);
270
+ const data = readValue(params[1]);
271
+ Joi.assert(filename, Joi.string());
272
+ fs.writeFileSync(filename, data);
273
+ return 0;
274
+ }
275
+ default: {
276
+ throw new Error(`Invalid method '${method}'`);
277
+ }
278
+ }
279
+ }
280
+ function readFile(filename) {
281
+ try {
282
+ // get the file from the working directory
283
+ return fs.readFileSync(filename, "utf8");
284
+ }
285
+ catch {
286
+ // otherwise get the file from the internal scripts of the CLI tool
287
+ try {
288
+ return fs.readFileSync(new URL(`scripts/${filename}`, import.meta.url), "utf8");
289
+ }
290
+ catch {
291
+ throw new Error(`Can't open script ${filename}`);
292
+ }
293
+ }
294
+ }
295
+ function readValue(input, required = false) {
296
+ if (!input || typeof input !== "string")
297
+ return input;
298
+ const parts = input.split(".");
299
+ let fieldName = parts[0];
300
+ if (rtVars[fieldName] === undefined) {
301
+ if (required)
302
+ return undefined;
303
+ return input;
304
+ }
305
+ let i = 0;
306
+ let obj = rtVars;
307
+ while (i < parts.length - 1) {
308
+ obj = obj[fieldName];
309
+ i += 1;
310
+ fieldName = parts[i];
311
+ }
312
+ return obj[fieldName];
313
+ }
314
+ async function run(filename, inputs = []) {
315
+ const filedata = readFile(filename);
316
+ const lines = filedata.split(/\r?\n/);
317
+ let response;
318
+ for (const [i, line_] of lines.entries()) {
319
+ let line = line_.trim();
320
+ if (line.length > 0 && !line.startsWith("#")) {
321
+ for (const [j, input] of inputs.entries()) {
322
+ const param = typeof input === "string" ? input : JSON.stringify(input);
323
+ line = line.replaceAll(new RegExp(`\\$${j + 1}`, "g"), param);
324
+ }
325
+ try {
326
+ response = await execCommand(line, true);
327
+ }
328
+ catch (error) {
329
+ utils.red(error.stack);
330
+ throw new Error(`Error in line ${i + 1}: ${error.message}`);
331
+ }
332
+ }
333
+ else {
334
+ console.log(line);
335
+ }
336
+ }
337
+ return response;
338
+ }
339
+ async function setDomain(dom) {
340
+ if (dom) {
341
+ for (const apiName of Object.keys(config.api)) {
342
+ config.api[apiName].url = config.api[apiName].url.replace(config.domain, dom);
343
+ }
344
+ config.domain = dom;
345
+ await setVar("domain", config.domain);
346
+ }
347
+ utils.yellow(`Current domain: ${config.domain}`);
348
+ }
349
+ async function setVar(key, value, printVar = true) {
350
+ lodashSet(rtVars, key, value);
351
+ if (key.startsWith("user.")) {
352
+ await execCommand("using user user");
353
+ return;
354
+ }
355
+ if (!printVar)
356
+ return;
357
+ utils.cyanBold(`Value saved in '${key}':`);
358
+ utils.cyan(value);
359
+ }
360
+ async function setVersionApis(version) {
361
+ if (version) {
362
+ config = loadConfig(config.env, version);
363
+ await initConfigVars();
364
+ utils.yellow(`Version ${config.versionApis} loaded`);
365
+ }
366
+ else {
367
+ utils.yellow(`Current version: ${config.versionApis}`);
368
+ }
369
+ const { api, contractAddresses, domain, env: envConfig } = config;
370
+ utils.yellow({
371
+ contractAddresses,
372
+ domain,
373
+ env: envConfig,
374
+ urls: Object.keys(api).map((name) => api[name].url),
375
+ version: config.versionApis,
376
+ });
377
+ }
378
+ async function smartContractApi(contract, method, ...inputs) {
379
+ const apiUrl = config.api[contract].url;
380
+ if (method === "get") {
381
+ const response = await utils.httpCall.get(`${apiUrl}${urlPath(inputs)}`, httpOpts);
382
+ return response.data;
383
+ }
384
+ if (method === "head") {
385
+ const response = await utils.httpCall.head(`${apiUrl}${urlPath(inputs)}`, httpOpts);
386
+ return response.data;
387
+ }
388
+ const url = `${apiUrl}/jsonrpc`;
389
+ if (contract === "tcr") {
390
+ throw new Error("TCR API does not have jsonrpc endpoint. Use `ledger tcr` instead.");
391
+ }
392
+ if (method === "sendSignedTransaction") {
393
+ const unsignedTransaction = readValue(inputs[0]);
394
+ const sgnTx = readValue(inputs[1]);
395
+ Joi.assert(unsignedTransaction, Joi.object());
396
+ Joi.assert(sgnTx, Joi.string());
397
+ const bodySend = jsonrpcBody(method, [
398
+ paramSignedTransaction(unsignedTransaction, sgnTx),
399
+ ]);
400
+ const response = await utils.httpCall.post(url, bodySend, httpOpts);
401
+ // use ledger proxy is besu provider is defined
402
+ const context = {
403
+ client,
404
+ config,
405
+ httpOpts,
406
+ rtVars,
407
+ token,
408
+ transactionInfo,
409
+ };
410
+ const useProxy = !!config.besuProvider;
411
+ transactionInfo.txId = response.data.result;
412
+ transactionInfo.receipt = await waitToBeMined(response.data.result, useProxy, context);
413
+ return transactionInfo.receipt;
414
+ }
415
+ if (!method.startsWith("build-")) {
416
+ const inputsStr = inputs
417
+ .map((input) => typeof input === "string" ? input : JSON.stringify(input))
418
+ .join(" ");
419
+ const uTx = await execCommand(`${contract} build-${method} ${inputsStr}`, true);
420
+ const sgnTx = await execCommand(`compute signTransaction ${JSON.stringify(uTx)}`, true);
421
+ await execCommand(`${contract} sendSignedTransaction ${JSON.stringify(uTx)} ${sgnTx}`, true);
422
+ const txInfo = await execCommand("view transactionInfo", true);
423
+ return txInfo;
424
+ }
425
+ let m = method.replace("build-", "");
426
+ const build = await buildParam(contract, m, client, inputs.map((input) => readValue(input)), config.versionApis);
427
+ if (build.method)
428
+ m = build.method.slice(0, Math.max(0, build.method.indexOf("(")));
429
+ const param = {
430
+ from: client.ethWallet.address,
431
+ ...build.param,
432
+ };
433
+ const body = jsonrpcBody(m, [param]);
434
+ const response = await utils.httpCall.post(url, body, httpOpts);
435
+ utils.yellow(build.info.title);
436
+ utils.yellow(build.info.data);
437
+ transactionInfo = {
438
+ build,
439
+ contract,
440
+ method: m,
441
+ };
442
+ return response.data.result;
443
+ }
444
+ function updateHttpOpts() {
445
+ const conformanceId = readValue("conformanceId", true);
446
+ httpOpts = {
447
+ headers: {
448
+ ...(token && {
449
+ authorization: `Bearer ${token}`,
450
+ }),
451
+ ...(conformanceId && { conformance: conformanceId }),
452
+ },
453
+ };
454
+ }
455
+ function urlPath(inputs) {
456
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
81
457
  return inputs.map((input) => readValue(input)).join("");
82
458
  }
83
459
  async function using(method, ...params) {
84
460
  switch (method) {
461
+ case "ethuser": {
462
+ if (params[0] === "null") {
463
+ const hw = client.hardwareWallet;
464
+ client = new Client();
465
+ client.hardwareWallet = hw;
466
+ await setVar("user", client.toJSON(), false);
467
+ utils.yellow("User removed");
468
+ return client;
469
+ }
470
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
471
+ await execCommand(`using user ES256K ${params.join(" ")}`, true);
472
+ return client;
473
+ }
85
474
  case "token": {
86
475
  token = params[0] === "empty" ? null : readValue(params[0]);
87
476
  updateHttpOpts();
@@ -97,7 +486,7 @@ async function using(method, ...params) {
97
486
  client.hardwareWallet = hw;
98
487
  client.did = existingUser.did;
99
488
  client.didVersion = existingUser.didVersion;
100
- client.privateKeyHex = existingUser.privateKeyHex;
489
+ client.keys = existingUser.keys;
101
490
  client.clientId = existingUser.clientId;
102
491
  client.accreditationUrl = existingUser.accreditationUrl;
103
492
  if (existingUser.accreditationUrl) {
@@ -105,10 +494,9 @@ async function using(method, ...params) {
105
494
  }
106
495
  client.proxyId = existingUser.proxyId;
107
496
  client.issuerState = existingUser.issuerState;
108
- if (existingUser.privateKeyHex) {
109
- client.ethWallet = new ethers.Wallet(existingUser.privateKeyHex);
497
+ if (existingUser.keys?.ES256K) {
498
+ client.ethWallet = new ethers.Wallet(existingUser.keys.ES256K.privateKeyHex);
110
499
  }
111
- client.keys = existingUser.keys;
112
500
  await setVar("user", client.toJSON(), false);
113
501
  await execCommand("view user", false);
114
502
  return client;
@@ -149,15 +537,14 @@ async function using(method, ...params) {
149
537
  }
150
538
  else {
151
539
  client.ethWallet = new ethers.Wallet(utils.encodePrivateKey({
152
- key: privateKey,
153
540
  format: "hex",
541
+ key: privateKey,
154
542
  }));
155
543
  }
156
- client.privateKeyHex = client.ethWallet.privateKey;
157
544
  const privateKeyJwk = utils.encodePrivateKey({
158
- key: client.ethWallet.privateKey,
159
545
  alg,
160
546
  format: "jwk",
547
+ key: client.ethWallet.privateKey,
161
548
  });
162
549
  client.setJwk(alg, privateKeyJwk);
163
550
  }
@@ -166,24 +553,24 @@ async function using(method, ...params) {
166
553
  client.hardwareWallet = new HardwareWallet(config.openSClib);
167
554
  const publicKeyJwk = client.hardwareWallet.getPublicKey();
168
555
  const publicKeyPem = utils.encodePublicKey({
169
- key: publicKeyJwk,
170
556
  alg,
171
557
  format: "pem",
558
+ key: publicKeyJwk,
172
559
  });
173
560
  client.keys.ES256 = {
174
561
  id: "",
562
+ isHardwareWallet: true,
175
563
  kid: "",
176
564
  privateKeyJwk: undefined,
177
565
  publicKeyJwk,
178
566
  publicKeyPem,
179
- isHardwareWallet: true,
180
567
  };
181
568
  }
182
569
  else if (alg === "ES256" && typeof privateKey === "string") {
183
570
  const privateKeyJwk = utils.encodePrivateKey({
184
- key: privateKey,
185
571
  alg,
186
572
  format: "jwk",
573
+ key: privateKey,
187
574
  });
188
575
  client.setJwk(alg, privateKeyJwk);
189
576
  }
@@ -215,8 +602,9 @@ async function using(method, ...params) {
215
602
  }
216
603
  break;
217
604
  }
218
- default:
605
+ default: {
219
606
  throw new Error("did method must be 'did1' or 'did2'");
607
+ }
220
608
  }
221
609
  client.keys[alg].id = verificationMethodId;
222
610
  client.keys[alg].kid = `${client.did}#${verificationMethodId}`;
@@ -224,392 +612,8 @@ async function using(method, ...params) {
224
612
  await execCommand("view user", false);
225
613
  return client;
226
614
  }
227
- case "ethuser": {
228
- if (params[0] === "null") {
229
- const hw = client.hardwareWallet;
230
- client = new Client();
231
- client.hardwareWallet = hw;
232
- await setVar("user", client.toJSON(), false);
233
- utils.yellow("User removed");
234
- return client;
235
- }
236
- await execCommand(`using user ES256K ${params.join(" ")}`, true);
237
- return client;
238
- }
239
- default:
615
+ default: {
240
616
  throw new Error(`Invalid subject '${method}'`);
241
- }
242
- }
243
- async function smartContractApi(contract, method, ...inputs) {
244
- const apiUrl = config.api[contract].url;
245
- if (method === "get") {
246
- const response = await utils.httpCall.get(`${apiUrl}${urlPath(inputs)}`, httpOpts);
247
- return response.data;
248
- }
249
- if (method === "head") {
250
- const response = await utils.httpCall.head(`${apiUrl}${urlPath(inputs)}`, httpOpts);
251
- return response.data;
252
- }
253
- const url = `${apiUrl}/jsonrpc`;
254
- if (method === "sendSignedTransaction") {
255
- const unsignedTransaction = readValue(inputs[0]);
256
- const sgnTx = readValue(inputs[1]);
257
- Joi.assert(unsignedTransaction, Joi.object());
258
- Joi.assert(sgnTx, Joi.string());
259
- const bodySend = jsonrpcBody(method, [
260
- paramSignedTransaction(unsignedTransaction, sgnTx),
261
- ]);
262
- const response = await utils.httpCall.post(url, bodySend, httpOpts);
263
- // use ledger proxy is besu provider is defined
264
- const context = {
265
- config,
266
- httpOpts,
267
- client,
268
- rtVars,
269
- transactionInfo,
270
- token,
271
- };
272
- const useProxy = !!config.besuProvider;
273
- transactionInfo.txId = response.data.result;
274
- transactionInfo.receipt = await waitToBeMined(response.data.result, useProxy, context);
275
- return transactionInfo.receipt;
276
- }
277
- if (!method.startsWith("build-")) {
278
- const inputsStr = inputs
279
- .map((input) => typeof input === "string" ? input : JSON.stringify(input))
280
- .join(" ");
281
- const uTx = await execCommand(`${contract} build-${method} ${inputsStr}`, true);
282
- const sgnTx = await execCommand(`compute signTransaction ${JSON.stringify(uTx)}`, true);
283
- await execCommand(`${contract} sendSignedTransaction ${JSON.stringify(uTx)} ${sgnTx}`, true);
284
- const txInfo = await execCommand("view transactionInfo", true);
285
- return txInfo;
286
- }
287
- let m = method.replace("build-", "");
288
- const build = await buildParam(contract, m, client, inputs.map((input) => readValue(input)), config.versionApis);
289
- if (build.method)
290
- m = build.method.substring(0, build.method.indexOf("("));
291
- const param = {
292
- from: client.ethWallet.address,
293
- ...build.param,
294
- };
295
- const body = jsonrpcBody(m, [param]);
296
- const response = await utils.httpCall.post(url, body, httpOpts);
297
- utils.yellow(build.info.title);
298
- utils.yellow(build.info.data);
299
- transactionInfo = {
300
- contract,
301
- method: m,
302
- build,
303
- };
304
- return response.data.result;
305
- }
306
- async function environment(env) {
307
- if (env) {
308
- config = loadConfig(env, config.versionApis);
309
- await initConfigVars();
310
- utils.yellow(`Environment ${env} loaded`);
311
- }
312
- else {
313
- utils.yellow(`Current environment: ${config.env}`);
314
- }
315
- const { domain, contractAddresses, api, env: envConfig } = config;
316
- utils.yellow({
317
- domain,
318
- contractAddresses,
319
- urls: Object.keys(api).map((name) => api[name].url),
320
- env: envConfig,
321
- version: config.versionApis,
322
- });
323
- }
324
- async function setVersionApis(version) {
325
- if (version) {
326
- config = loadConfig(config.env, version);
327
- await initConfigVars();
328
- utils.yellow(`Version ${config.versionApis} loaded`);
329
- }
330
- else {
331
- utils.yellow(`Current version: ${config.versionApis}`);
332
- }
333
- const { domain, contractAddresses, api, env: envConfig } = config;
334
- utils.yellow({
335
- domain,
336
- contractAddresses,
337
- urls: Object.keys(api).map((name) => api[name].url),
338
- env: envConfig,
339
- version: config.versionApis,
340
- });
341
- }
342
- async function setDomain(dom) {
343
- if (dom) {
344
- Object.keys(config.api).forEach((apiName) => {
345
- config.api[apiName].url = config.api[apiName].url.replace(config.domain, dom);
346
- });
347
- config.domain = dom;
348
- await setVar("domain", config.domain);
349
- }
350
- utils.yellow(`Current domain: ${config.domain}`);
351
- }
352
- function fileSystem(method, ...params) {
353
- switch (method) {
354
- case "readBinaryFile": {
355
- const [filename] = params;
356
- Joi.assert(filename, Joi.string());
357
- return fs.readFileSync(filename);
358
- }
359
- case "writeBinaryFile": {
360
- const filename = readValue(params[0]);
361
- const data = readValue(params[1]);
362
- Joi.assert(filename, Joi.string());
363
- fs.writeFileSync(filename, data);
364
- return 0;
365
- }
366
- default:
367
- throw new Error(`Invalid method '${method}'`);
368
- }
369
- }
370
- function readFile(filename) {
371
- try {
372
- // get the file from the working directory
373
- return fs.readFileSync(filename, "utf-8");
374
- }
375
- catch {
376
- // otherwise get the file from the internal scripts of the CLI tool
377
- try {
378
- return fs.readFileSync(new URL(`scripts/${filename}`, import.meta.url), "utf8");
379
- }
380
- catch {
381
- throw new Error(`Can't open script ${filename}`);
382
- }
383
- }
384
- }
385
- async function run(filename, inputs = []) {
386
- const filedata = readFile(filename);
387
- const lines = filedata.split(/\r?\n/);
388
- let response;
389
- for (let i = 0; i < lines.length; i += 1) {
390
- let line = lines[i].trim();
391
- if (line.length > 0 && !line.startsWith("#")) {
392
- inputs.forEach((input, j) => {
393
- const param = typeof input === "string" ? input : JSON.stringify(input);
394
- line = line.replace(new RegExp(`\\$${j + 1}`, "g"), param);
395
- });
396
- try {
397
- response = await execCommand(line, true);
398
- }
399
- catch (error) {
400
- utils.red(error.stack);
401
- throw new Error(`Error in line ${i + 1}: ${error.message}`);
402
- }
403
- }
404
- else {
405
- console.log(line);
406
- }
407
- }
408
- return response;
409
- }
410
- export async function execCommand(command, printCommand = false) {
411
- if (printCommand)
412
- console.log(`==> ${command}`);
413
- const parts = parseLine(command);
414
- let varName = "";
415
- if (parts.length === 0)
416
- return 0;
417
- if (typeof parts[0] === "string" && parts[0].includes(":")) {
418
- varName = parts[0].replace(":", "");
419
- parts.shift();
420
- }
421
- const [part0, part1] = parts;
422
- const parts2 = parts.slice(2);
423
- const method = part0;
424
- const word1 = part1;
425
- Joi.assert(method, Joi.string());
426
- // APIs linked to smart contracts
427
- if (["timestamp", "did", "tir", "tsr", "tpr", "tnt"].includes(method)) {
428
- Joi.assert(word1, Joi.string());
429
- const response = await smartContractApi(method, word1, ...parts2);
430
- if (varName)
431
- await setVar(varName, response);
432
- return response;
433
- }
434
- const schemaStrings = Joi.array().items(Joi.string());
435
- const context = {
436
- config,
437
- httpOpts,
438
- client,
439
- rtVars,
440
- transactionInfo,
441
- token,
442
- };
443
- // Other APIs and configurations
444
- switch (method) {
445
- case "fs": {
446
- Joi.assert(word1, Joi.string());
447
- const response = fileSystem(word1, ...parts2);
448
- console.log(response);
449
- if (varName)
450
- await setVar(varName, response);
451
- return response;
452
- }
453
- case "set": {
454
- Joi.assert(word1, Joi.string());
455
- const inputs = parts2.map((p) => readValue(p));
456
- let value = inputs[0];
457
- if (inputs.length > 1) {
458
- Joi.assert(inputs, schemaStrings);
459
- value = inputs.join("");
460
- }
461
- await setVar(word1, value);
462
- return value;
463
- }
464
- case "view": {
465
- Joi.assert(word1, Joi.string());
466
- return view([readValue(word1)], context);
467
- }
468
- case "using": {
469
- Joi.assert(word1, Joi.string());
470
- const user = await using(word1, ...parts2);
471
- return user;
472
- }
473
- case "hardwarewallet":
474
- case "hw": {
475
- Joi.assert(word1, Joi.string());
476
- const inputs = parts2.map((p) => {
477
- if (word1 === "setpin")
478
- return p;
479
- return readValue(p);
480
- });
481
- const response = await hardwarewallet(word1, inputs, context);
482
- if (varName)
483
- await setVar(varName, response);
484
- return response;
485
- }
486
- case "env": {
487
- Joi.assert(word1, Joi.string().optional());
488
- const input = readValue(word1);
489
- await environment(input);
490
- return 0;
491
- }
492
- case "version": {
493
- Joi.assert(word1, Joi.string().optional());
494
- const input = readValue(word1);
495
- await setVersionApis(input);
496
- return 0;
497
- }
498
- case "domain": {
499
- Joi.assert(word1, Joi.string());
500
- await setDomain(word1);
501
- return 0;
502
- }
503
- case "get": {
504
- Joi.assert(word1, Joi.string());
505
- const input = readValue(word1);
506
- const response = await utils.httpCall.get(input, context.httpOpts);
507
- if (varName)
508
- await setVar(varName, response.data);
509
- return response.data;
510
- }
511
- case "authorisation": {
512
- Joi.assert(word1, Joi.string());
513
- Joi.assert(parts2, schemaStrings);
514
- const inputs = parts2.map((p) => readValue(p));
515
- const response = await authorisationV4(word1, inputs, context);
516
- if (varName)
517
- await setVar(varName, response);
518
- return response;
519
- }
520
- case "proxyledger": {
521
- Joi.assert(word1, Joi.string());
522
- const inputs = parts2.map((p) => readValue(p));
523
- const response = await ledgerV4(word1, inputs, true, context);
524
- if (varName)
525
- await setVar(varName, response);
526
- return response;
527
- }
528
- case "ledger": {
529
- Joi.assert(word1, Joi.string());
530
- const inputs = parts2.map((p) => readValue(p));
531
- const response = await ledgerV4(word1, inputs, false, context);
532
- if (varName)
533
- await setVar(varName, response);
534
- return response;
535
- }
536
- case "tnl": {
537
- Joi.assert(word1, Joi.string());
538
- const inputs = parts2.map((p) => readValue(p));
539
- const response = await tnl(word1, inputs, context);
540
- if (varName)
541
- await setVar(varName, response);
542
- return response;
543
- }
544
- case "conformance": {
545
- Joi.assert(word1, Joi.string());
546
- const inputs = parts2.map((p) => readValue(p));
547
- const response = await conformanceV3(word1, inputs, context);
548
- if (varName)
549
- await setVar(varName, response);
550
- return response;
551
- }
552
- case "compute": {
553
- Joi.assert(word1, Joi.string());
554
- const inputs = parts2.map((p) => readValue(p));
555
- const response = await compute(word1, inputs, context);
556
- if (varName)
557
- await setVar(varName, response);
558
- return response;
559
- }
560
- case "load": {
561
- const filename = readValue(word1);
562
- const data = readFile(filename);
563
- let response;
564
- try {
565
- response = JSON.parse(data);
566
- }
567
- catch {
568
- response = data;
569
- }
570
- if (varName)
571
- await setVar(varName, response);
572
- return response;
573
- }
574
- case "export": {
575
- let data = readValue(word1);
576
- const filename = readValue(parts2[0]);
577
- if (typeof data === "object") {
578
- data = JSON.stringify(data, null, 2);
579
- }
580
- const absolutePath = path.isAbsolute(filename)
581
- ? filename
582
- : path.join(`${process.cwd()}`, filename);
583
- fs.writeFileSync(filename, data);
584
- utils.yellow(`file saved at ${absolutePath}`);
585
- return undefined;
586
- }
587
- case "run": {
588
- Joi.assert(word1, Joi.string());
589
- const inputs = parts2.map((p) => readValue(p));
590
- const response = await run(word1, inputs);
591
- if (varName)
592
- await setVar(varName, response);
593
- return response;
594
- }
595
- case "exit": {
596
- process.exit(0);
597
- return 0;
598
- }
599
- default:
600
- throw new Error(`Invalid method '${method}'`);
601
- }
602
- }
603
- export async function main() {
604
- await initConfigVars();
605
- // eslint-disable-next-line no-constant-condition
606
- while (true) {
607
- const command = await utils.askQuestion("==> ");
608
- try {
609
- await execCommand(command);
610
- }
611
- catch (error) {
612
- utils.red(error.stack);
613
617
  }
614
618
  }
615
619
  }