@pythnetwork/price-pusher 10.2.0 → 10.3.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 (127) hide show
  1. package/{lib/aptos/aptos.js → dist/aptos/aptos.cjs} +80 -76
  2. package/{lib → dist}/aptos/aptos.d.ts +5 -5
  3. package/{lib/aptos/balance-tracker.js → dist/aptos/balance-tracker.cjs} +37 -25
  4. package/{lib → dist}/aptos/balance-tracker.d.ts +9 -9
  5. package/dist/aptos/command.cjs +161 -0
  6. package/{lib → dist}/aptos/command.d.ts +1 -2
  7. package/dist/common.cjs +4 -0
  8. package/{lib → dist}/common.d.ts +0 -1
  9. package/{lib/controller.js → dist/controller.cjs} +35 -33
  10. package/{lib → dist}/controller.d.ts +5 -6
  11. package/dist/evm/balance-tracker.cjs +58 -0
  12. package/{lib → dist}/evm/balance-tracker.d.ts +10 -10
  13. package/dist/evm/command.cjs +205 -0
  14. package/{lib → dist}/evm/command.d.ts +1 -2
  15. package/dist/evm/custom-gas-station.cjs +54 -0
  16. package/{lib → dist}/evm/custom-gas-station.d.ts +1 -2
  17. package/dist/evm/evm.cjs +287 -0
  18. package/{lib → dist}/evm/evm.d.ts +8 -7
  19. package/{lib/evm/pyth-abi.js → dist/evm/pyth-abi.cjs} +181 -160
  20. package/{lib → dist}/evm/pyth-abi.d.ts +0 -1
  21. package/dist/evm/pyth-contract.cjs +17 -0
  22. package/{lib → dist}/evm/pyth-contract.d.ts +3 -4
  23. package/dist/evm/super-wallet.cjs +90 -0
  24. package/{lib → dist}/evm/super-wallet.d.ts +1 -2
  25. package/dist/fuel/command.cjs +135 -0
  26. package/{lib → dist}/fuel/command.d.ts +1 -2
  27. package/dist/fuel/fuel.cjs +108 -0
  28. package/{lib → dist}/fuel/fuel.d.ts +5 -5
  29. package/dist/index.cjs +25 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/injective/command.cjs +150 -0
  32. package/{lib → dist}/injective/command.d.ts +1 -2
  33. package/{lib/injective/injective.js → dist/injective/injective.cjs} +100 -98
  34. package/{lib → dist}/injective/injective.d.ts +7 -6
  35. package/dist/interface.cjs +142 -0
  36. package/{lib → dist}/interface.d.ts +12 -13
  37. package/dist/metrics.cjs +218 -0
  38. package/{lib → dist}/metrics.d.ts +10 -11
  39. package/dist/near/command.cjs +129 -0
  40. package/{lib → dist}/near/command.d.ts +1 -2
  41. package/dist/near/near.cjs +183 -0
  42. package/{lib → dist}/near/near.d.ts +5 -5
  43. package/dist/options.cjs +132 -0
  44. package/{lib → dist}/options.d.ts +1 -2
  45. package/dist/package.json +1 -0
  46. package/dist/price-config.cjs +104 -0
  47. package/{lib → dist}/price-config.d.ts +5 -6
  48. package/{lib/pyth-price-listener.js → dist/pyth-price-listener.cjs} +30 -24
  49. package/{lib → dist}/pyth-price-listener.d.ts +4 -4
  50. package/dist/solana/balance-tracker.cjs +60 -0
  51. package/{lib → dist}/solana/balance-tracker.d.ts +9 -9
  52. package/dist/solana/command.cjs +259 -0
  53. package/{lib → dist}/solana/command.d.ts +2 -3
  54. package/{lib/solana/solana.js → dist/solana/solana.cjs} +90 -78
  55. package/{lib → dist}/solana/solana.d.ts +6 -6
  56. package/dist/sui/balance-tracker.cjs +58 -0
  57. package/{lib → dist}/sui/balance-tracker.d.ts +9 -9
  58. package/dist/sui/command.cjs +190 -0
  59. package/{lib → dist}/sui/command.d.ts +1 -2
  60. package/{lib/sui/sui.js → dist/sui/sui.cjs} +145 -133
  61. package/{lib → dist}/sui/sui.d.ts +7 -8
  62. package/dist/ton/command.cjs +137 -0
  63. package/{lib → dist}/ton/command.d.ts +1 -2
  64. package/dist/ton/ton.cjs +103 -0
  65. package/{lib → dist}/ton/ton.d.ts +7 -6
  66. package/dist/utils.cjs +102 -0
  67. package/{lib → dist}/utils.d.ts +4 -4
  68. package/package.json +161 -20
  69. package/lib/aptos/aptos.d.ts.map +0 -1
  70. package/lib/aptos/balance-tracker.d.ts.map +0 -1
  71. package/lib/aptos/command.d.ts.map +0 -1
  72. package/lib/aptos/command.js +0 -126
  73. package/lib/common.d.ts.map +0 -1
  74. package/lib/common.js +0 -2
  75. package/lib/controller.d.ts.map +0 -1
  76. package/lib/evm/balance-tracker.d.ts.map +0 -1
  77. package/lib/evm/balance-tracker.js +0 -49
  78. package/lib/evm/command.d.ts.map +0 -1
  79. package/lib/evm/command.js +0 -178
  80. package/lib/evm/custom-gas-station.d.ts.map +0 -1
  81. package/lib/evm/custom-gas-station.js +0 -40
  82. package/lib/evm/evm.d.ts.map +0 -1
  83. package/lib/evm/evm.js +0 -270
  84. package/lib/evm/pyth-abi.d.ts.map +0 -1
  85. package/lib/evm/pyth-contract.d.ts.map +0 -1
  86. package/lib/evm/pyth-contract.js +0 -11
  87. package/lib/evm/super-wallet.d.ts.map +0 -1
  88. package/lib/evm/super-wallet.js +0 -73
  89. package/lib/fuel/command.d.ts.map +0 -1
  90. package/lib/fuel/command.js +0 -98
  91. package/lib/fuel/fuel.d.ts.map +0 -1
  92. package/lib/fuel/fuel.js +0 -101
  93. package/lib/index.d.ts +0 -3
  94. package/lib/index.d.ts.map +0 -1
  95. package/lib/index.js +0 -34
  96. package/lib/injective/command.d.ts.map +0 -1
  97. package/lib/injective/command.js +0 -119
  98. package/lib/injective/injective.d.ts.map +0 -1
  99. package/lib/interface.d.ts.map +0 -1
  100. package/lib/interface.js +0 -122
  101. package/lib/metrics.d.ts.map +0 -1
  102. package/lib/metrics.js +0 -152
  103. package/lib/near/command.d.ts.map +0 -1
  104. package/lib/near/command.js +0 -103
  105. package/lib/near/near.d.ts.map +0 -1
  106. package/lib/near/near.js +0 -168
  107. package/lib/options.d.ts.map +0 -1
  108. package/lib/options.js +0 -84
  109. package/lib/price-config.d.ts.map +0 -1
  110. package/lib/price-config.js +0 -114
  111. package/lib/pyth-price-listener.d.ts.map +0 -1
  112. package/lib/solana/balance-tracker.d.ts.map +0 -1
  113. package/lib/solana/balance-tracker.js +0 -51
  114. package/lib/solana/command.d.ts.map +0 -1
  115. package/lib/solana/command.js +0 -223
  116. package/lib/solana/solana.d.ts.map +0 -1
  117. package/lib/sui/balance-tracker.d.ts.map +0 -1
  118. package/lib/sui/balance-tracker.js +0 -49
  119. package/lib/sui/command.d.ts.map +0 -1
  120. package/lib/sui/command.js +0 -160
  121. package/lib/sui/sui.d.ts.map +0 -1
  122. package/lib/ton/command.d.ts.map +0 -1
  123. package/lib/ton/command.js +0 -99
  124. package/lib/ton/ton.d.ts.map +0 -1
  125. package/lib/ton/ton.js +0 -97
  126. package/lib/utils.d.ts.map +0 -1
  127. package/lib/utils.js +0 -61
@@ -0,0 +1,190 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-explicit-any */ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "default", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return _default;
9
+ }
10
+ });
11
+ const _nodefs = /*#__PURE__*/ _interop_require_default(require("node:fs"));
12
+ const _client = require("@mysten/sui/client");
13
+ const _ed25519 = require("@mysten/sui/keypairs/ed25519");
14
+ const _hermesclient = require("@pythnetwork/hermes-client");
15
+ const _pino = /*#__PURE__*/ _interop_require_default(require("pino"));
16
+ const _controller = require("../controller.cjs");
17
+ const _metrics = require("../metrics.cjs");
18
+ const _options = /*#__PURE__*/ _interop_require_wildcard(require("../options.cjs"));
19
+ const _priceconfig = require("../price-config.cjs");
20
+ const _pythpricelistener = require("../pyth-price-listener.cjs");
21
+ const _balancetracker = require("./balance-tracker.cjs");
22
+ const _sui = require("./sui.cjs");
23
+ const _utils = require("../utils.cjs");
24
+ function _interop_require_default(obj) {
25
+ return obj && obj.__esModule ? obj : {
26
+ default: obj
27
+ };
28
+ }
29
+ function _getRequireWildcardCache(nodeInterop) {
30
+ if (typeof WeakMap !== "function") return null;
31
+ var cacheBabelInterop = new WeakMap();
32
+ var cacheNodeInterop = new WeakMap();
33
+ return (_getRequireWildcardCache = function(nodeInterop) {
34
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
35
+ })(nodeInterop);
36
+ }
37
+ function _interop_require_wildcard(obj, nodeInterop) {
38
+ if (!nodeInterop && obj && obj.__esModule) {
39
+ return obj;
40
+ }
41
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
42
+ return {
43
+ default: obj
44
+ };
45
+ }
46
+ var cache = _getRequireWildcardCache(nodeInterop);
47
+ if (cache && cache.has(obj)) {
48
+ return cache.get(obj);
49
+ }
50
+ var newObj = {
51
+ __proto__: null
52
+ };
53
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
54
+ for(var key in obj){
55
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
56
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
57
+ if (desc && (desc.get || desc.set)) {
58
+ Object.defineProperty(newObj, key, desc);
59
+ } else {
60
+ newObj[key] = obj[key];
61
+ }
62
+ }
63
+ }
64
+ newObj.default = obj;
65
+ if (cache) {
66
+ cache.set(obj, newObj);
67
+ }
68
+ return newObj;
69
+ }
70
+ const _default = {
71
+ command: "sui",
72
+ describe: "Run price pusher for sui. Most of the arguments below are" + "network specific, so there's one set of values for mainnet and" + " another for testnet. See config.sui.mainnet.sample.json for the " + "appropriate values for your network. ",
73
+ builder: {
74
+ endpoint: {
75
+ description: "RPC endpoint URL for sui. The pusher will periodically" + "poll for updates. The polling interval is configurable via the " + "`polling-frequency` command-line argument.",
76
+ type: "string",
77
+ required: true
78
+ },
79
+ "pyth-state-id": {
80
+ description: "Pyth State Id. Can be found here" + "https://docs.pyth.network/documentation/pythnet-price-feeds/sui",
81
+ type: "string",
82
+ required: true
83
+ },
84
+ "wormhole-state-id": {
85
+ description: "Wormhole State Id. Can be found here" + "https://docs.pyth.network/documentation/pythnet-price-feeds/sui",
86
+ type: "string",
87
+ required: true
88
+ },
89
+ "num-gas-objects": {
90
+ description: "Number of gas objects in the pool.",
91
+ type: "number",
92
+ required: true,
93
+ default: 30
94
+ },
95
+ "ignore-gas-objects": {
96
+ description: "Gas objects to ignore when merging gas objects on startup -- use this for locked objects.",
97
+ type: "array",
98
+ required: false,
99
+ default: []
100
+ },
101
+ "gas-budget": {
102
+ description: "Gas budget for each price update",
103
+ type: "number",
104
+ required: true,
105
+ default: 500_000_000
106
+ },
107
+ "account-index": {
108
+ description: "Index of the account to use derived by the mnemonic",
109
+ type: "number",
110
+ required: true,
111
+ default: 0
112
+ },
113
+ ..._options.priceConfigFile,
114
+ ..._options.priceServiceEndpoint,
115
+ ..._options.mnemonicFile,
116
+ ..._options.pollingFrequency,
117
+ ..._options.pushingFrequency,
118
+ ..._options.logLevel,
119
+ ..._options.controllerLogLevel,
120
+ ..._options.enableMetrics,
121
+ ..._options.metricsPort
122
+ },
123
+ handler: async function(argv) {
124
+ const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pushingFrequency, pollingFrequency, pythStateId, wormholeStateId, numGasObjects, ignoreGasObjects, gasBudget, accountIndex, logLevel, controllerLogLevel, enableMetrics, metricsPort } = argv;
125
+ const logger = (0, _pino.default)({
126
+ level: logLevel
127
+ });
128
+ const priceConfigs = (0, _priceconfig.readPriceConfigFile)(priceConfigFile);
129
+ const hermesClient = new _hermesclient.HermesClient(priceServiceEndpoint);
130
+ const mnemonic = _nodefs.default.readFileSync(mnemonicFile, "utf8").trim();
131
+ const keypair = _ed25519.Ed25519Keypair.deriveKeypair(mnemonic, `m/44'/784'/${accountIndex}'/0'/0'`);
132
+ const suiAddress = keypair.getPublicKey().toSuiAddress();
133
+ logger.info(`Pushing updates from wallet address: ${suiAddress}`);
134
+ let priceItems = priceConfigs.map(({ id, alias })=>({
135
+ id,
136
+ alias
137
+ }));
138
+ // Better to filter out invalid price items before creating the pyth listener
139
+ const { existingPriceItems, invalidPriceItems } = await (0, _utils.filterInvalidPriceItems)(hermesClient, priceItems);
140
+ if (invalidPriceItems.length > 0) {
141
+ logger.error(`Invalid price id submitted for: ${invalidPriceItems.map(({ alias })=>alias).join(", ")}`);
142
+ }
143
+ priceItems = existingPriceItems;
144
+ // Initialize metrics if enabled
145
+ let metrics;
146
+ if (enableMetrics) {
147
+ metrics = new _metrics.PricePusherMetrics(logger.child({
148
+ module: "Metrics"
149
+ }));
150
+ metrics.start(metricsPort);
151
+ logger.info(`Metrics server started on port ${metricsPort}`);
152
+ }
153
+ const pythListener = new _pythpricelistener.PythPriceListener(hermesClient, priceItems, logger.child({
154
+ module: "PythPriceListener"
155
+ }));
156
+ const suiClient = new _client.SuiClient({
157
+ url: endpoint
158
+ });
159
+ const suiListener = new _sui.SuiPriceListener(pythStateId, wormholeStateId, endpoint, priceItems, logger.child({
160
+ module: "SuiPriceListener"
161
+ }), {
162
+ pollingFrequency
163
+ });
164
+ const suiPusher = await _sui.SuiPricePusher.createWithAutomaticGasPool(hermesClient, logger.child({
165
+ module: "SuiPricePusher"
166
+ }), pythStateId, wormholeStateId, endpoint, keypair, gasBudget, numGasObjects, ignoreGasObjects);
167
+ const controller = new _controller.Controller(priceConfigs, pythListener, suiListener, suiPusher, logger.child({
168
+ module: "Controller"
169
+ }, {
170
+ level: controllerLogLevel
171
+ }), {
172
+ pushingFrequency,
173
+ metrics: metrics
174
+ });
175
+ // Create and start the balance tracker if metrics are enabled
176
+ if (metrics) {
177
+ const balanceTracker = (0, _balancetracker.createSuiBalanceTracker)({
178
+ client: suiClient,
179
+ address: suiAddress,
180
+ network: "sui",
181
+ updateInterval: pushingFrequency,
182
+ metrics,
183
+ logger
184
+ });
185
+ // Start the balance tracker
186
+ await balanceTracker.start();
187
+ }
188
+ void controller.start();
189
+ }
190
+ };
@@ -1,4 +1,4 @@
1
- import { Options } from "yargs";
1
+ import type { Options } from "yargs";
2
2
  declare const _default: {
3
3
  command: string;
4
4
  describe: string;
@@ -23,4 +23,3 @@ declare const _default: {
23
23
  handler: (argv: any) => Promise<void>;
24
24
  };
25
25
  export default _default;
26
- //# sourceMappingURL=command.d.ts.map
@@ -1,22 +1,39 @@
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 pyth_sui_js_1 = require("@pythnetwork/pyth-sui-js");
6
- const transactions_1 = require("@mysten/sui/transactions");
7
- const client_1 = require("@mysten/sui/client");
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get SuiPriceListener () {
13
+ return SuiPriceListener;
14
+ },
15
+ get SuiPricePusher () {
16
+ return SuiPricePusher;
17
+ }
18
+ });
19
+ const _client = require("@mysten/sui/client");
20
+ const _transactions = require("@mysten/sui/transactions");
21
+ const _pythsuijs = require("@pythnetwork/pyth-sui-js");
22
+ const _interface = require("../interface.cjs");
8
23
  const GAS_FEE_FOR_SPLIT = 2_000_000_000;
9
24
  // TODO: read this from on chain config
10
25
  const MAX_NUM_GAS_OBJECTS_IN_PTB = 256;
11
26
  const MAX_NUM_OBJECTS_IN_ARGUMENT = 510;
12
- class SuiPriceListener extends interface_1.ChainPriceListener {
27
+ class SuiPriceListener extends _interface.ChainPriceListener {
13
28
  pythClient;
14
29
  provider;
15
30
  logger;
16
- constructor(pythStateId, wormholeStateId, endpoint, priceItems, logger, config) {
31
+ constructor(pythStateId, wormholeStateId, endpoint, priceItems, logger, config){
17
32
  super(config.pollingFrequency, priceItems);
18
- this.provider = new client_1.SuiClient({ url: endpoint });
19
- this.pythClient = new pyth_sui_js_1.SuiPythClient(this.provider, pythStateId, wormholeStateId);
33
+ this.provider = new _client.SuiClient({
34
+ url: endpoint
35
+ });
36
+ this.pythClient = new _pythsuijs.SuiPythClient(this.provider, pythStateId, wormholeStateId);
20
37
  this.logger = logger;
21
38
  }
22
39
  async getOnChainPriceInfo(priceId) {
@@ -28,47 +45,29 @@ class SuiPriceListener extends interface_1.ChainPriceListener {
28
45
  // Fetching the price info object for the above priceInfoObjectId
29
46
  const priceInfoObject = await this.provider.getObject({
30
47
  id: priceInfoObjectId,
31
- options: { showContent: true },
48
+ options: {
49
+ showContent: true
50
+ }
32
51
  });
33
- if (!priceInfoObject.data || !priceInfoObject.data.content)
34
- throw new Error("Price not found on chain for price id " + priceId);
35
- if (priceInfoObject.data.content.dataType !== "moveObject")
36
- throw new Error("fetched object datatype should be moveObject");
37
- const priceInfo =
38
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
52
+ if (!priceInfoObject.data?.content) throw new Error("Price not found on chain for price id " + priceId);
53
+ if (priceInfoObject.data.content.dataType !== "moveObject") throw new Error("fetched object datatype should be moveObject");
54
+ const priceInfo = // eslint-disable-next-line @typescript-eslint/ban-ts-comment
39
55
  // @ts-ignore
40
- priceInfoObject.data.content.fields.price_info.fields.price_feed.fields
41
- .price.fields;
56
+ priceInfoObject.data.content.fields.price_info.fields.price_feed.fields.price.fields;
42
57
  const { magnitude, negative } = priceInfo.price.fields;
43
58
  const conf = priceInfo.conf;
44
59
  const timestamp = priceInfo.timestamp;
45
60
  return {
46
- price: negative ? "-" + magnitude : magnitude,
61
+ price: negative ? `-${magnitude}` : magnitude,
47
62
  conf,
48
- publishTime: Number(timestamp),
63
+ publishTime: Number(timestamp)
49
64
  };
50
- }
51
- catch (err) {
52
- this.logger.error(err, `Polling Sui on-chain price for ${priceId} failed.`);
65
+ } catch (error) {
66
+ this.logger.error(error, `Polling Sui on-chain price for ${priceId} failed.`);
53
67
  return undefined;
54
68
  }
55
69
  }
56
70
  }
57
- exports.SuiPriceListener = SuiPriceListener;
58
- /**
59
- * The `SuiPricePusher` is designed for high-throughput of price updates.
60
- * Achieving this property requires sacrificing some nice-to-have features of other
61
- * pusher implementations that can reduce cost when running multiple pushers. It also requires
62
- * jumping through some Sui-specific hoops in order to maximize parallelism.
63
- *
64
- * The two main design features are:
65
- * 1. This implementation does not use `update_price_feeds_if_necssary` and simulate the transaction
66
- * before submission. If multiple instances of this pusher are running in parallel, all of them will
67
- * land all of their pushed updates on-chain.
68
- * 2. The pusher will split the Coin balance in the provided account into a pool of different Coin objects.
69
- * Each transaction will be allocated a Coin object from this pool as needed. This process enables the
70
- * transactions to avoid referencing the same owned objects, which allows them to be processed in parallel.
71
- */
72
71
  class SuiPricePusher {
73
72
  signer;
74
73
  provider;
@@ -77,7 +76,7 @@ class SuiPricePusher {
77
76
  gasBudget;
78
77
  gasPool;
79
78
  pythClient;
80
- constructor(signer, provider, logger, hermesClient, gasBudget, gasPool, pythClient) {
79
+ constructor(signer, provider, logger, hermesClient, gasBudget, gasPool, pythClient){
81
80
  this.signer = signer;
82
81
  this.provider = provider;
83
82
  this.logger = logger;
@@ -87,21 +86,16 @@ class SuiPricePusher {
87
86
  this.pythClient = pythClient;
88
87
  }
89
88
  /**
90
- * getPackageId returns the latest package id that the object belongs to. Use this to
91
- * fetch the latest package id for a given object id and handle package upgrades automatically.
92
- * @param provider
93
- * @param objectId
94
- * @returns package id
95
- */
96
- static async getPackageId(provider, objectId) {
97
- const state = await provider
98
- .getObject({
89
+ * getPackageId returns the latest package id that the object belongs to. Use this to
90
+ * fetch the latest package id for a given object id and handle package upgrades automatically.
91
+ * @returns package id
92
+ */ static async getPackageId(provider, objectId) {
93
+ const state = await provider.getObject({
99
94
  id: objectId,
100
95
  options: {
101
- showContent: true,
102
- },
103
- })
104
- .then((result) => {
96
+ showContent: true
97
+ }
98
+ }).then((result)=>{
105
99
  if (result.data?.content?.dataType == "moveObject") {
106
100
  return result.data.content.fields;
107
101
  }
@@ -115,24 +109,24 @@ class SuiPricePusher {
115
109
  throw new Error("upgrade_cap not found");
116
110
  }
117
111
  /**
118
- * Create a price pusher with a pool of `numGasObjects` gas coins that will be used to send transactions.
119
- * The gas coins of the wallet for the provided keypair will be merged and then evenly split into `numGasObjects`.
120
- */
121
- static async createWithAutomaticGasPool(hermesClient, logger, pythStateId, wormholeStateId, endpoint, keypair, gasBudget, numGasObjects, ignoreGasObjects) {
112
+ * Create a price pusher with a pool of `numGasObjects` gas coins that will be used to send transactions.
113
+ * The gas coins of the wallet for the provided keypair will be merged and then evenly split into `numGasObjects`.
114
+ */ static async createWithAutomaticGasPool(hermesClient, logger, pythStateId, wormholeStateId, endpoint, keypair, gasBudget, numGasObjects, ignoreGasObjects) {
122
115
  if (numGasObjects > MAX_NUM_OBJECTS_IN_ARGUMENT) {
123
116
  throw new Error(`numGasObjects cannot be greater than ${MAX_NUM_OBJECTS_IN_ARGUMENT} until we implement split chunking`);
124
117
  }
125
- const provider = new client_1.SuiClient({ url: endpoint });
118
+ const provider = new _client.SuiClient({
119
+ url: endpoint
120
+ });
126
121
  const gasPool = await SuiPricePusher.initializeGasPool(keypair, provider, numGasObjects, ignoreGasObjects, logger);
127
- const pythClient = new pyth_sui_js_1.SuiPythClient(provider, pythStateId, wormholeStateId);
122
+ const pythClient = new _pythsuijs.SuiPythClient(provider, pythStateId, wormholeStateId);
128
123
  return new SuiPricePusher(keypair, provider, logger, hermesClient, gasBudget, gasPool, pythClient);
129
124
  }
130
125
  async updatePriceFeed(priceIds, pubTimesToPush) {
131
126
  if (priceIds.length === 0) {
132
127
  return;
133
128
  }
134
- if (priceIds.length !== pubTimesToPush.length)
135
- throw new Error("Invalid arguments");
129
+ if (priceIds.length !== pubTimesToPush.length) throw new Error("Invalid arguments");
136
130
  if (this.gasPool.length === 0) {
137
131
  this.logger.warn("Skipping update: no available gas coin.");
138
132
  return;
@@ -140,27 +134,27 @@ class SuiPricePusher {
140
134
  // 3 price feeds per transaction is the optimal number for gas cost.
141
135
  const priceIdChunks = chunkArray(priceIds, 3);
142
136
  const txBlocks = [];
143
- await Promise.all(priceIdChunks.map(async (priceIdChunk) => {
137
+ await Promise.all(priceIdChunks.map(async (priceIdChunk)=>{
144
138
  const response = await this.hermesClient.getLatestPriceUpdates(priceIdChunk, {
145
139
  encoding: "base64",
146
- ignoreInvalidPriceIds: true,
140
+ ignoreInvalidPriceIds: true
147
141
  });
148
142
  if (response.binary.data.length !== 1) {
149
143
  throw new Error(`Expected a single VAA for all priceIds ${priceIdChunk} but received ${response.binary.data.length} VAAs: ${response.binary.data}`);
150
144
  }
151
145
  const vaa = response.binary.data[0];
152
- const tx = new transactions_1.Transaction();
153
- await this.pythClient.updatePriceFeeds(tx, [Buffer.from(vaa, "base64")], priceIdChunk);
146
+ const tx = new _transactions.Transaction();
147
+ await this.pythClient.updatePriceFeeds(tx, [
148
+ Buffer.from(vaa ?? "", "base64")
149
+ ], priceIdChunk);
154
150
  txBlocks.push(tx);
155
151
  }));
156
152
  await this.sendTransactionBlocks(txBlocks);
157
153
  }
158
- /** Send every transaction in txs in parallel, returning when all transactions have completed. */
159
- async sendTransactionBlocks(txs) {
160
- return Promise.all(txs.map((tx) => this.sendTransactionBlock(tx)));
154
+ /** Send every transaction in txs in parallel, returning when all transactions have completed. */ async sendTransactionBlocks(txs) {
155
+ return Promise.all(txs.map((tx)=>this.sendTransactionBlock(tx)));
161
156
  }
162
- /** Send a single transaction block using a gas coin from the pool. */
163
- async sendTransactionBlock(tx) {
157
+ /** Send a single transaction block using a gas coin from the pool. */ async sendTransactionBlock(tx) {
164
158
  const gasObject = this.gasPool.shift();
165
159
  if (gasObject === undefined) {
166
160
  this.logger.warn("No available gas coin. Skipping push.");
@@ -168,29 +162,28 @@ class SuiPricePusher {
168
162
  }
169
163
  let nextGasObject = undefined;
170
164
  try {
171
- tx.setGasPayment([gasObject]);
165
+ tx.setGasPayment([
166
+ gasObject
167
+ ]);
172
168
  tx.setGasBudget(this.gasBudget);
173
169
  const result = await this.provider.signAndExecuteTransaction({
174
170
  signer: this.signer,
175
171
  transaction: tx,
176
172
  options: {
177
- showEffects: true,
178
- },
173
+ showEffects: true
174
+ }
179
175
  });
180
- nextGasObject = result.effects?.mutated
181
- ?.map((obj) => obj.reference)
182
- .find((ref) => ref.objectId === gasObject.objectId);
183
- this.logger.info({ hash: result.digest }, "Successfully updated price with transaction digest");
184
- }
185
- catch (err) {
186
- if (String(err).includes("Balance of gas object") ||
187
- String(err).includes("GasBalanceTooLow")) {
188
- this.logger.error(err, "Insufficient gas balance");
176
+ nextGasObject = result.effects?.mutated?.map((obj)=>obj.reference).find((ref)=>ref.objectId === gasObject.objectId);
177
+ this.logger.info({
178
+ hash: result.digest
179
+ }, "Successfully updated price with transaction digest");
180
+ } catch (error) {
181
+ if (String(error).includes("Balance of gas object") || String(error).includes("GasBalanceTooLow")) {
182
+ this.logger.error(error, "Insufficient gas balance");
189
183
  // If the error is caused by insufficient gas, we should panic
190
- throw err;
191
- }
192
- else {
193
- this.logger.error(err, "Failed to update price. Trying to refresh gas object references.");
184
+ throw error;
185
+ } else {
186
+ this.logger.error(error, "Failed to update price. Trying to refresh gas object references.");
194
187
  // Refresh the coin object here in case the error is caused by an object version mismatch.
195
188
  nextGasObject = await SuiPricePusher.tryRefreshObjectReference(this.provider, gasObject);
196
189
  }
@@ -206,85 +199,98 @@ class SuiPricePusher {
206
199
  static async initializeGasPool(signer, provider, numGasObjects, ignoreGasObjects, logger) {
207
200
  const signerAddress = signer.toSuiAddress();
208
201
  if (ignoreGasObjects.length > 0) {
209
- logger.info({ ignoreGasObjects }, "Ignoring some gas objects for coin merging");
202
+ logger.info({
203
+ ignoreGasObjects
204
+ }, "Ignoring some gas objects for coin merging");
210
205
  }
211
206
  const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne(signer, provider, signerAddress, ignoreGasObjects, logger);
212
207
  const coinResult = await provider.getObject({
213
208
  id: consolidatedCoin.objectId,
214
- options: { showContent: true },
209
+ options: {
210
+ showContent: true
211
+ }
215
212
  });
216
213
  let balance;
217
- if (coinResult.data &&
218
- coinResult.data.content &&
219
- coinResult.data.content.dataType == "moveObject") {
214
+ if (coinResult.data?.content && coinResult.data.content.dataType == "moveObject") {
220
215
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
221
216
  // @ts-ignore
222
217
  balance = coinResult.data.content.fields.balance;
223
- }
224
- else
225
- throw new Error("Bad coin object");
218
+ } else throw new Error("Bad coin object");
226
219
  const splitAmount = (BigInt(balance) - BigInt(GAS_FEE_FOR_SPLIT)) / BigInt(numGasObjects);
227
220
  const gasPool = await SuiPricePusher.splitGasCoinEqually(signer, provider, signerAddress, Number(splitAmount), numGasObjects, consolidatedCoin);
228
- logger.info({ gasPool }, "Gas pool is filled with coins");
221
+ logger.info({
222
+ gasPool
223
+ }, "Gas pool is filled with coins");
229
224
  return gasPool;
230
225
  }
231
226
  // Attempt to refresh the version of the provided object reference to point to the current version
232
227
  // of the object. Throws an error if the object cannot be refreshed.
233
228
  static async tryRefreshObjectReference(provider, ref) {
234
- const objectResponse = await provider.getObject({ id: ref.objectId });
235
- if (objectResponse.data !== undefined) {
229
+ const objectResponse = await provider.getObject({
230
+ id: ref.objectId
231
+ });
232
+ if (objectResponse.data === undefined) {
233
+ throw new Error("Failed to refresh object reference");
234
+ } else {
236
235
  return {
237
236
  digest: objectResponse.data.digest,
238
237
  objectId: objectResponse.data.objectId,
239
- version: objectResponse.data.version,
238
+ version: objectResponse.data.version
240
239
  };
241
240
  }
242
- else {
243
- throw new Error("Failed to refresh object reference");
244
- }
245
241
  }
246
242
  static async getAllGasCoins(provider, owner) {
247
243
  let hasNextPage = true;
248
244
  let cursor;
249
245
  const coins = new Set([]);
250
246
  let numCoins = 0;
251
- while (hasNextPage) {
247
+ while(hasNextPage){
252
248
  const paginatedCoins = await provider.getCoins({
253
249
  owner,
254
- cursor,
250
+ cursor
255
251
  });
256
252
  numCoins += paginatedCoins.data.length;
257
- paginatedCoins.data.forEach((c) => coins.add(JSON.stringify({
253
+ for (const c of paginatedCoins.data)coins.add(JSON.stringify({
258
254
  objectId: c.coinObjectId,
259
255
  version: c.version,
260
- digest: c.digest,
261
- })));
256
+ digest: c.digest
257
+ }));
262
258
  hasNextPage = paginatedCoins.hasNextPage;
263
259
  cursor = paginatedCoins.nextCursor;
264
260
  }
265
261
  if (numCoins !== coins.size) {
266
262
  throw new Error("Unexpected getCoins result: duplicate coins found");
267
263
  }
268
- return [...coins].map((item) => JSON.parse(item));
264
+ return [
265
+ ...coins
266
+ ].map((item)=>JSON.parse(item));
269
267
  }
270
268
  static async splitGasCoinEqually(signer, provider, signerAddress, splitAmount, numGasObjects, gasCoin) {
271
269
  // TODO: implement chunking if numGasObjects exceeds MAX_NUM_CREATED_OBJECTS
272
- const tx = new transactions_1.Transaction();
273
- const coins = tx.splitCoins(tx.gas, Array.from({ length: numGasObjects }, () => tx.pure.u64(splitAmount)));
274
- tx.transferObjects(Array.from({ length: numGasObjects }, (_, i) => coins[i]), tx.pure.address(signerAddress));
275
- tx.setGasPayment([gasCoin]);
270
+ const tx = new _transactions.Transaction();
271
+ const coins = tx.splitCoins(tx.gas, Array.from({
272
+ length: numGasObjects
273
+ }, ()=>tx.pure.u64(splitAmount)));
274
+ tx.transferObjects(Array.from({
275
+ length: numGasObjects
276
+ }, (_, i)=>coins[i]), tx.pure.address(signerAddress));
277
+ tx.setGasPayment([
278
+ gasCoin
279
+ ]);
276
280
  const result = await provider.signAndExecuteTransaction({
277
281
  signer,
278
282
  transaction: tx,
279
- options: { showEffects: true },
283
+ options: {
284
+ showEffects: true
285
+ }
280
286
  });
281
- const error = result?.effects?.status.error;
287
+ const error = result.effects?.status.error;
282
288
  if (error) {
283
289
  throw new Error(`Failed to initialize gas pool: ${error}. Try re-running the script`);
284
290
  }
285
- const newCoins = result.effects.created.map((obj) => obj.reference);
291
+ const newCoins = result.effects.created.map((obj)=>obj.reference);
286
292
  if (newCoins.length !== numGasObjects) {
287
- throw new Error(`Failed to initialize gas pool. Expected ${numGasObjects}, got: ${newCoins}`);
293
+ throw new Error(`Failed to initialize gas pool. Expected ${numGasObjects}, got: ${JSON.stringify(newCoins)}`);
288
294
  }
289
295
  return newCoins;
290
296
  }
@@ -297,13 +303,16 @@ class SuiPricePusher {
297
303
  const gasCoinsChunks = chunkArray(gasCoins, MAX_NUM_GAS_OBJECTS_IN_PTB - 2);
298
304
  let finalCoin;
299
305
  const lockedAddresses = new Set();
300
- initialLockedAddresses.forEach((value) => lockedAddresses.add(value));
301
- for (let i = 0; i < gasCoinsChunks.length; i++) {
302
- const mergeTx = new transactions_1.Transaction();
306
+ for (const value of initialLockedAddresses)lockedAddresses.add(value);
307
+ for(let i = 0; i < gasCoinsChunks.length; i++){
308
+ const mergeTx = new _transactions.Transaction();
303
309
  let coins = gasCoinsChunks[i];
304
- coins = coins.filter((coin) => !lockedAddresses.has(coin.objectId));
310
+ coins = coins?.filter((coin)=>!lockedAddresses.has(coin.objectId)) ?? [];
305
311
  if (finalCoin) {
306
- coins = [finalCoin, ...coins];
312
+ coins = [
313
+ finalCoin,
314
+ ...coins
315
+ ];
307
316
  }
308
317
  mergeTx.setGasPayment(coins);
309
318
  let mergeResult;
@@ -311,14 +320,17 @@ class SuiPricePusher {
311
320
  mergeResult = await provider.signAndExecuteTransaction({
312
321
  signer,
313
322
  transaction: mergeTx,
314
- options: { showEffects: true },
323
+ options: {
324
+ showEffects: true
325
+ }
315
326
  });
316
- }
317
- catch (err) {
318
- logger.error(err, "Merge transaction failed with error");
319
- if (String(err).includes("quorum of validators because of locked objects. Retried a conflicting transaction")) {
320
- Object.values(err.data).forEach((lockedObjects) => {
321
- lockedObjects.forEach((lockedObject) => {
327
+ } catch (error_) {
328
+ logger.error(error_, "Merge transaction failed with error");
329
+ if (String(error_).includes("quorum of validators because of locked objects. Retried a conflicting transaction")) {
330
+ // eslint-disable-next-line unicorn/no-array-for-each
331
+ Object.values(error_.data).forEach((lockedObjects)=>{
332
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, unicorn/no-array-for-each
333
+ lockedObjects.forEach((lockedObject)=>{
322
334
  lockedAddresses.add(lockedObject[0]);
323
335
  });
324
336
  });
@@ -326,22 +338,22 @@ class SuiPricePusher {
326
338
  i--;
327
339
  continue;
328
340
  }
329
- throw err;
341
+ throw error_;
330
342
  }
331
- const error = mergeResult?.effects?.status.error;
343
+ const error = mergeResult.effects?.status.error;
332
344
  if (error) {
333
345
  throw new Error(`Failed to merge coins when initializing gas pool: ${error}. Try re-running the script`);
334
346
  }
335
- finalCoin = mergeResult.effects.mutated.map((obj) => obj.reference)[0];
347
+ finalCoin = mergeResult.effects.mutated.map((obj)=>obj.reference)[0];
336
348
  }
349
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
337
350
  return finalCoin;
338
351
  }
339
352
  }
340
- exports.SuiPricePusher = SuiPricePusher;
341
353
  function chunkArray(array, size) {
342
354
  const chunked = [];
343
355
  let index = 0;
344
- while (index < array.length) {
356
+ while(index < array.length){
345
357
  chunked.push(array.slice(index, size + index));
346
358
  index += size;
347
359
  }