@pythnetwork/price-pusher 4.1.2 → 5.4.2

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 (48) hide show
  1. package/README.md +36 -2
  2. package/lib/aptos/aptos.d.ts +31 -0
  3. package/lib/aptos/aptos.d.ts.map +1 -0
  4. package/lib/aptos/aptos.js +139 -0
  5. package/lib/aptos/command.d.ts +18 -0
  6. package/lib/aptos/command.d.ts.map +1 -0
  7. package/lib/aptos/command.js +82 -0
  8. package/lib/common.d.ts +5 -0
  9. package/lib/common.d.ts.map +1 -0
  10. package/lib/common.js +2 -0
  11. package/lib/controller.d.ts +2 -1
  12. package/lib/controller.d.ts.map +1 -0
  13. package/lib/controller.js +10 -2
  14. package/lib/evm/command.d.ts +1 -0
  15. package/lib/evm/command.d.ts.map +1 -0
  16. package/lib/evm/command.js +3 -0
  17. package/lib/evm/custom-gas-station.d.ts +1 -0
  18. package/lib/evm/custom-gas-station.d.ts.map +1 -0
  19. package/lib/evm/evm.d.ts +1 -0
  20. package/lib/evm/evm.d.ts.map +1 -0
  21. package/lib/evm/evm.js +9 -1
  22. package/lib/index.d.ts +1 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +4 -0
  25. package/lib/injective/command.d.ts +2 -0
  26. package/lib/injective/command.d.ts.map +1 -0
  27. package/lib/injective/command.js +13 -2
  28. package/lib/injective/injective.d.ts +2 -0
  29. package/lib/injective/injective.d.ts.map +1 -0
  30. package/lib/injective/injective.js +35 -34
  31. package/lib/interface.d.ts +1 -0
  32. package/lib/interface.d.ts.map +1 -0
  33. package/lib/options.d.ts +1 -0
  34. package/lib/options.d.ts.map +1 -0
  35. package/lib/price-config.d.ts +10 -1
  36. package/lib/price-config.d.ts.map +1 -0
  37. package/lib/price-config.js +39 -10
  38. package/lib/pyth-price-listener.d.ts +1 -0
  39. package/lib/pyth-price-listener.d.ts.map +1 -0
  40. package/lib/sui/command.d.ts +24 -0
  41. package/lib/sui/command.d.ts.map +1 -0
  42. package/lib/sui/command.js +132 -0
  43. package/lib/sui/sui.d.ts +42 -0
  44. package/lib/sui/sui.d.ts.map +1 -0
  45. package/lib/sui/sui.js +358 -0
  46. package/lib/utils.d.ts +1 -0
  47. package/lib/utils.d.ts.map +1 -0
  48. package/package.json +5 -3
package/lib/sui/sui.js ADDED
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SuiPricePusher = exports.SuiPriceListener = void 0;
4
+ const interface_1 = require("../interface");
5
+ const sui_js_1 = require("@mysten/sui.js");
6
+ const GAS_FEE_FOR_SPLIT = 2_000_000_000;
7
+ // TODO: read this from on chain config
8
+ const MAX_NUM_GAS_OBJECTS_IN_PTB = 256;
9
+ const MAX_NUM_OBJECTS_IN_ARGUMENT = 510;
10
+ class SuiPriceListener extends interface_1.ChainPriceListener {
11
+ pythPackageId;
12
+ priceFeedToPriceInfoObjectTableId;
13
+ endpoint;
14
+ constructor(pythPackageId, priceFeedToPriceInfoObjectTableId, endpoint, priceItems, config) {
15
+ super("sui", config.pollingFrequency, priceItems);
16
+ this.pythPackageId = pythPackageId;
17
+ this.priceFeedToPriceInfoObjectTableId = priceFeedToPriceInfoObjectTableId;
18
+ this.endpoint = endpoint;
19
+ }
20
+ async getOnChainPriceInfo(priceId) {
21
+ try {
22
+ const provider = new sui_js_1.JsonRpcProvider(new sui_js_1.Connection({ fullnode: this.endpoint }));
23
+ const priceInfoObjectId = await priceIdToPriceInfoObjectId(provider, this.pythPackageId, this.priceFeedToPriceInfoObjectTableId, priceId);
24
+ // Fetching the price info object for the above priceInfoObjectId
25
+ const priceInfoObject = await provider.getObject({
26
+ id: priceInfoObjectId,
27
+ options: { showContent: true },
28
+ });
29
+ if (priceInfoObject.data === undefined ||
30
+ priceInfoObject.data.content === undefined)
31
+ throw new Error("Price not found on chain for price id " + priceId);
32
+ if (priceInfoObject.data.content.dataType !== "moveObject")
33
+ throw new Error("fetched object datatype should be moveObject");
34
+ const { magnitude, negative } = priceInfoObject.data.content.fields.price_info.fields.price_feed.fields
35
+ .price.fields.price.fields;
36
+ const conf = priceInfoObject.data.content.fields.price_info.fields.price_feed.fields
37
+ .price.fields.conf;
38
+ const timestamp = priceInfoObject.data.content.fields.price_info.fields.price_feed.fields
39
+ .price.fields.timestamp;
40
+ return {
41
+ price: negative ? "-" + magnitude : magnitude,
42
+ conf,
43
+ publishTime: Number(timestamp),
44
+ };
45
+ }
46
+ catch (e) {
47
+ console.error(`Polling Sui on-chain price for ${priceId} failed. Error:`);
48
+ console.error(e);
49
+ return undefined;
50
+ }
51
+ }
52
+ }
53
+ exports.SuiPriceListener = SuiPriceListener;
54
+ class SuiPricePusher {
55
+ signer;
56
+ priceServiceConnection;
57
+ pythPackageId;
58
+ pythStateId;
59
+ wormholePackageId;
60
+ wormholeStateId;
61
+ priceFeedToPriceInfoObjectTableId;
62
+ maxVaasPerPtb;
63
+ gasBudget;
64
+ gasPool;
65
+ constructor(signer, priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, gasPool) {
66
+ this.signer = signer;
67
+ this.priceServiceConnection = priceServiceConnection;
68
+ this.pythPackageId = pythPackageId;
69
+ this.pythStateId = pythStateId;
70
+ this.wormholePackageId = wormholePackageId;
71
+ this.wormholeStateId = wormholeStateId;
72
+ this.priceFeedToPriceInfoObjectTableId = priceFeedToPriceInfoObjectTableId;
73
+ this.maxVaasPerPtb = maxVaasPerPtb;
74
+ this.gasBudget = gasBudget;
75
+ this.gasPool = gasPool;
76
+ }
77
+ /**
78
+ * Create a price pusher with a pool of `numGasObjects` gas coins that will be used to send transactions.
79
+ * The gas coins of the wallet for the provided mnemonic will be merged and then evenly split into `numGasObjects`.
80
+ */
81
+ static async createWithAutomaticGasPool(priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, numGasObjects) {
82
+ if (numGasObjects > MAX_NUM_OBJECTS_IN_ARGUMENT) {
83
+ throw new Error(`numGasObjects cannot be greater than ${MAX_NUM_OBJECTS_IN_ARGUMENT} until we implement split chunking`);
84
+ }
85
+ const signer = new sui_js_1.RawSigner(sui_js_1.Ed25519Keypair.deriveKeypair(mnemonic), new sui_js_1.JsonRpcProvider(new sui_js_1.Connection({ fullnode: endpoint })));
86
+ const gasPool = await SuiPricePusher.initializeGasPool(signer, numGasObjects);
87
+ return new SuiPricePusher(signer, priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, gasPool);
88
+ }
89
+ async updatePriceFeed(priceIds, pubTimesToPush) {
90
+ if (priceIds.length === 0) {
91
+ return;
92
+ }
93
+ if (priceIds.length !== pubTimesToPush.length)
94
+ throw new Error("Invalid arguments");
95
+ if (this.gasPool.length === 0) {
96
+ console.warn("Skipping update: no available gas coin.");
97
+ return;
98
+ }
99
+ const priceFeeds = await this.priceServiceConnection.getLatestPriceFeeds(priceIds);
100
+ if (priceFeeds === undefined) {
101
+ console.warn("Failed to fetch price updates. Skipping push.");
102
+ return;
103
+ }
104
+ const vaaToPriceFeedIds = new Map();
105
+ for (const priceFeed of priceFeeds) {
106
+ // The ! will succeed as long as the priceServiceConnection is configured to return binary vaa data (which it is).
107
+ const vaa = priceFeed.getVAA();
108
+ if (!vaaToPriceFeedIds.has(vaa)) {
109
+ vaaToPriceFeedIds.set(vaa, []);
110
+ }
111
+ vaaToPriceFeedIds.get(vaa).push(priceFeed.id);
112
+ }
113
+ const txs = [];
114
+ let currentBatchVaas = [];
115
+ let currentBatchPriceFeedIds = [];
116
+ for (const [vaa, priceFeedIds] of vaaToPriceFeedIds.entries()) {
117
+ currentBatchVaas.push(vaa);
118
+ currentBatchPriceFeedIds.push(...priceFeedIds);
119
+ if (currentBatchVaas.length >= this.maxVaasPerPtb) {
120
+ const tx = await this.createPriceUpdateTransaction(currentBatchVaas, currentBatchPriceFeedIds);
121
+ if (tx !== undefined) {
122
+ txs.push(tx);
123
+ }
124
+ currentBatchVaas = [];
125
+ currentBatchPriceFeedIds = [];
126
+ }
127
+ }
128
+ await this.sendTransactionBlocks(txs);
129
+ }
130
+ async createPriceUpdateTransaction(vaas, priceIds) {
131
+ const tx = new sui_js_1.TransactionBlock();
132
+ // Parse our batch price attestation VAA bytes using Wormhole.
133
+ // Check out the Wormhole cross-chain bridge and generic messaging protocol here:
134
+ // https://github.com/wormhole-foundation/wormhole
135
+ let verified_vaas = [];
136
+ for (const vaa of vaas) {
137
+ const [verified_vaa] = tx.moveCall({
138
+ target: `${this.wormholePackageId}::vaa::parse_and_verify`,
139
+ arguments: [
140
+ tx.object(this.wormholeStateId),
141
+ tx.pure([...Buffer.from(vaa, "base64")]),
142
+ tx.object(sui_js_1.SUI_CLOCK_OBJECT_ID),
143
+ ],
144
+ });
145
+ verified_vaas = verified_vaas.concat(verified_vaa);
146
+ }
147
+ // Create a hot potato vector of price feed updates that will
148
+ // be used to update price feeds.
149
+ let [price_updates_hot_potato] = tx.moveCall({
150
+ target: `${this.pythPackageId}::pyth::create_price_infos_hot_potato`,
151
+ arguments: [
152
+ tx.object(this.pythStateId),
153
+ tx.makeMoveVec({
154
+ type: `${this.wormholePackageId}::vaa::VAA`,
155
+ objects: verified_vaas,
156
+ }),
157
+ tx.object(sui_js_1.SUI_CLOCK_OBJECT_ID),
158
+ ],
159
+ });
160
+ // Update each price info object (containing our price feeds of interest)
161
+ // using the hot potato vector.
162
+ for (const priceId of priceIds) {
163
+ let priceInfoObjectId;
164
+ try {
165
+ priceInfoObjectId = await priceIdToPriceInfoObjectId(this.signer.provider, this.pythPackageId, this.priceFeedToPriceInfoObjectTableId, priceId);
166
+ }
167
+ catch (e) {
168
+ console.log("Error fetching price info object id for ", priceId);
169
+ console.error(e);
170
+ return undefined;
171
+ }
172
+ const coin = tx.splitCoins(tx.gas, [tx.pure(1)]);
173
+ [price_updates_hot_potato] = tx.moveCall({
174
+ target: `${this.pythPackageId}::pyth::update_single_price_feed`,
175
+ arguments: [
176
+ tx.object(this.pythStateId),
177
+ price_updates_hot_potato,
178
+ tx.object(priceInfoObjectId),
179
+ coin,
180
+ tx.object(sui_js_1.SUI_CLOCK_OBJECT_ID),
181
+ ],
182
+ });
183
+ }
184
+ // Explicitly destroy the hot potato vector, since it can't be dropped
185
+ // automatically.
186
+ tx.moveCall({
187
+ target: `${this.pythPackageId}::hot_potato_vector::destroy`,
188
+ arguments: [price_updates_hot_potato],
189
+ typeArguments: [`${this.pythPackageId}::price_info::PriceInfo`],
190
+ });
191
+ return tx;
192
+ }
193
+ /** Send every transaction in txs in parallel, returning when all transactions have completed. */
194
+ async sendTransactionBlocks(txs) {
195
+ return Promise.all(txs.map((tx) => this.sendTransactionBlock(tx)));
196
+ }
197
+ /** Send a single transaction block using a gas coin from the pool. */
198
+ async sendTransactionBlock(tx) {
199
+ const gasObject = this.gasPool.shift();
200
+ if (gasObject === undefined) {
201
+ console.warn("No available gas coin. Skipping push.");
202
+ return;
203
+ }
204
+ let nextGasObject = undefined;
205
+ try {
206
+ tx.setGasPayment([gasObject]);
207
+ tx.setGasBudget(this.gasBudget);
208
+ const result = await this.signer.signAndExecuteTransactionBlock({
209
+ transactionBlock: tx,
210
+ options: {
211
+ showEffects: true,
212
+ },
213
+ });
214
+ nextGasObject = (0, sui_js_1.getTransactionEffects)(result)
215
+ ?.mutated?.map((obj) => obj.reference)
216
+ .find((ref) => ref.objectId === gasObject.objectId);
217
+ console.log("Successfully updated price with transaction digest ", result.digest);
218
+ }
219
+ catch (e) {
220
+ console.log("Error when signAndExecuteTransactionBlock");
221
+ if (String(e).includes("GasBalanceTooLow")) {
222
+ console.warn(`The balance of gas object ${gasObject.objectId} is too low. Removing from pool.`);
223
+ }
224
+ else {
225
+ nextGasObject = gasObject;
226
+ }
227
+ console.error(e);
228
+ }
229
+ if (nextGasObject !== undefined) {
230
+ this.gasPool.push(nextGasObject);
231
+ }
232
+ }
233
+ // This function will smash all coins owned by the signer into one, and then
234
+ // split them equally into numGasObjects.
235
+ static async initializeGasPool(signer, numGasObjects) {
236
+ const signerAddress = await signer.getAddress();
237
+ const { totalBalance: balance } = await signer.provider.getBalance({
238
+ owner: signerAddress,
239
+ });
240
+ const splitAmount = (BigInt(balance) - BigInt(GAS_FEE_FOR_SPLIT)) / BigInt(numGasObjects);
241
+ const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne(signer, signerAddress);
242
+ const gasPool = await SuiPricePusher.splitGasCoinEqually(signer, signerAddress, Number(splitAmount), numGasObjects, consolidatedCoin);
243
+ console.log("Gas pool is filled with coins: ", gasPool);
244
+ return gasPool;
245
+ }
246
+ static async getAllGasCoins(provider, owner) {
247
+ let hasNextPage = true;
248
+ let cursor;
249
+ const coins = new Set([]);
250
+ let numCoins = 0;
251
+ while (hasNextPage) {
252
+ const paginatedCoins = await provider.getCoins({
253
+ owner,
254
+ cursor,
255
+ });
256
+ numCoins += paginatedCoins.data.length;
257
+ paginatedCoins.data.forEach((c) => coins.add(JSON.stringify({
258
+ objectId: c.coinObjectId,
259
+ version: c.version,
260
+ digest: c.digest,
261
+ })));
262
+ hasNextPage = paginatedCoins.hasNextPage;
263
+ cursor = paginatedCoins.nextCursor;
264
+ }
265
+ if (numCoins !== coins.size) {
266
+ throw new Error("Unexpected getCoins result: duplicate coins found");
267
+ }
268
+ return [...coins].map((item) => JSON.parse(item));
269
+ }
270
+ static async splitGasCoinEqually(signer, signerAddress, splitAmount, numGasObjects, gasCoin) {
271
+ // TODO: implement chunking if numGasObjects exceeds MAX_NUM_CREATED_OBJECTS
272
+ const tx = new sui_js_1.TransactionBlock();
273
+ const coins = tx.splitCoins(tx.gas, Array.from({ length: numGasObjects }, () => tx.pure(splitAmount)));
274
+ tx.transferObjects(Array.from({ length: numGasObjects }, (_, i) => coins[i]), tx.pure(signerAddress));
275
+ tx.setGasPayment([gasCoin]);
276
+ const result = await signer.signAndExecuteTransactionBlock({
277
+ transactionBlock: tx,
278
+ options: { showEffects: true },
279
+ });
280
+ const error = (0, sui_js_1.getExecutionStatusError)(result);
281
+ if (error) {
282
+ throw new Error(`Failed to initialize gas pool: ${error}. Try re-running the script`);
283
+ }
284
+ const newCoins = (0, sui_js_1.getCreatedObjects)(result).map((obj) => obj.reference);
285
+ if (newCoins.length !== numGasObjects) {
286
+ throw new Error(`Failed to initialize gas pool. Expected ${numGasObjects}, got: ${newCoins}`);
287
+ }
288
+ return newCoins;
289
+ }
290
+ static async mergeGasCoinsIntoOne(signer, owner) {
291
+ const gasCoins = await SuiPricePusher.getAllGasCoins(signer.provider, owner);
292
+ // skip merging if there is only one coin
293
+ if (gasCoins.length === 1) {
294
+ return gasCoins[0];
295
+ }
296
+ const gasCoinsChunks = chunkArray(gasCoins, MAX_NUM_GAS_OBJECTS_IN_PTB - 2);
297
+ let finalCoin;
298
+ for (let i = 0; i < gasCoinsChunks.length; i++) {
299
+ const mergeTx = new sui_js_1.TransactionBlock();
300
+ let coins = gasCoinsChunks[i];
301
+ if (finalCoin) {
302
+ coins = [finalCoin, ...coins];
303
+ }
304
+ mergeTx.setGasPayment(coins);
305
+ const mergeResult = await signer.signAndExecuteTransactionBlock({
306
+ transactionBlock: mergeTx,
307
+ options: { showEffects: true },
308
+ });
309
+ const error = (0, sui_js_1.getExecutionStatusError)(mergeResult);
310
+ if (error) {
311
+ throw new Error(`Failed to merge coins when initializing gas pool: ${error}. Try re-running the script`);
312
+ }
313
+ finalCoin = (0, sui_js_1.getTransactionEffects)(mergeResult).mutated.map((obj) => obj.reference)[0];
314
+ }
315
+ return finalCoin;
316
+ }
317
+ }
318
+ exports.SuiPricePusher = SuiPricePusher;
319
+ // We are calculating stored price info object id for given price id
320
+ // The mapping between which is static. Hence, we are caching it here.
321
+ const CACHE = {};
322
+ // For given priceid, this method will fetch the price info object id
323
+ // where the price information for the corresponding price feed is stored
324
+ async function priceIdToPriceInfoObjectId(provider, pythPackageId, priceFeedToPriceInfoObjectTableId, priceId) {
325
+ // Check if this was fetched before.
326
+ if (CACHE[priceId] !== undefined)
327
+ return CACHE[priceId];
328
+ const storedObjectID = await provider.getDynamicFieldObject({
329
+ parentId: priceFeedToPriceInfoObjectTableId,
330
+ name: {
331
+ type: `${pythPackageId}::price_identifier::PriceIdentifier`,
332
+ value: {
333
+ bytes: "0x" + priceId,
334
+ },
335
+ },
336
+ });
337
+ if (storedObjectID.error !== undefined)
338
+ throw storedObjectID.error;
339
+ if (storedObjectID.data === undefined ||
340
+ storedObjectID.data.content === undefined)
341
+ throw new Error("Price not found on chain for price id " + priceId);
342
+ if (storedObjectID.data.content.dataType !== "moveObject")
343
+ throw new Error("fetched object datatype should be moveObject");
344
+ // This ID points to the price info object for the given price id stored on chain
345
+ const priceInfoObjectId = storedObjectID.data.content.fields.value;
346
+ // cache the price info object id
347
+ CACHE[priceId] = priceInfoObjectId;
348
+ return priceInfoObjectId;
349
+ }
350
+ function chunkArray(array, size) {
351
+ const chunked = [];
352
+ let index = 0;
353
+ while (index < array.length) {
354
+ chunked.push(array.slice(index, size + index));
355
+ index += size;
356
+ }
357
+ return chunked;
358
+ }
package/lib/utils.d.ts CHANGED
@@ -10,3 +10,4 @@ export declare function removeLeading0x(id: HexString): HexString;
10
10
  export declare function addLeading0x(id: HexString): HexString;
11
11
  export declare function isWsEndpoint(endpoint: string): boolean;
12
12
  export declare function verifyValidOption<options extends Readonly<Array<any>>, validOption extends options[number]>(option: any, validOptions: options): validOption;
13
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAE9D,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACvC,eAAO,MAAM,QAAQ,uCAAwC,CAAC;AAC9D,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9C,eAAO,MAAM,iBAAiB,gBAAiB,CAAC;AAChD,MAAM,MAAM,gBAAgB,GAAG,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAEhE,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErD;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAKxD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAKrD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAStD;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,SAAS,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EACpC,WAAW,SAAS,OAAO,CAAC,MAAM,CAAC,EACnC,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,eAOnC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pythnetwork/price-pusher",
3
- "version": "4.1.2",
3
+ "version": "5.4.2",
4
4
  "description": "Pyth Price Pusher",
5
5
  "homepage": "https://pyth.network",
6
6
  "main": "lib/index.js",
@@ -51,15 +51,17 @@
51
51
  "typescript": "^4.6.3"
52
52
  },
53
53
  "dependencies": {
54
- "@injectivelabs/sdk-ts": "^1.0.484",
54
+ "@injectivelabs/sdk-ts": "1.10.72",
55
+ "@mysten/sui.js": "^0.34.0",
55
56
  "@pythnetwork/price-service-client": "*",
56
57
  "@pythnetwork/pyth-sdk-solidity": "*",
57
58
  "@truffle/hdwallet-provider": "^2.1.3",
59
+ "aptos": "^1.8.5",
58
60
  "joi": "^17.6.0",
59
61
  "web3": "^1.8.1",
60
62
  "web3-eth-contract": "^1.8.1",
61
63
  "yaml": "^2.1.1",
62
64
  "yargs": "^17.5.1"
63
65
  },
64
- "gitHead": "61a66eecaa8c6480509e32df56d826ee484eefae"
66
+ "gitHead": "5e44fa4c95ae7120ee8a05f145b27470d2c48e7b"
65
67
  }