@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,287 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/require-await */ "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 EvmPriceListener () {
13
+ return EvmPriceListener;
14
+ },
15
+ get EvmPricePusher () {
16
+ return EvmPricePusher;
17
+ }
18
+ });
19
+ const _viem = require("viem");
20
+ const _utils = require("../utils.cjs");
21
+ const _interface = require("../interface.cjs");
22
+ class EvmPriceListener extends _interface.ChainPriceListener {
23
+ pythContract;
24
+ watchEvents;
25
+ logger;
26
+ constructor(pythContract, priceItems, watchEvents, logger, config){
27
+ super(config.pollingFrequency, priceItems), this.pythContract = pythContract, this.watchEvents = watchEvents, this.logger = logger;
28
+ this.pythContract = pythContract;
29
+ this.logger = logger;
30
+ }
31
+ // This method should be awaited on and once it finishes it has the latest value
32
+ // for the given price feeds (if they exist).
33
+ async start() {
34
+ if (this.watchEvents) {
35
+ this.logger.info("Watching target network pyth contract events...");
36
+ void this.startWatching();
37
+ } else {
38
+ this.logger.info("The target network RPC endpoint is not Websocket. " + "Listening for updates only via polling....");
39
+ }
40
+ // base class for polling
41
+ await super.start();
42
+ }
43
+ async startWatching() {
44
+ this.pythContract.watchEvent.PriceFeedUpdate({
45
+ id: this.priceItems.map((item)=>(0, _utils.addLeading0x)(item.id))
46
+ }, {
47
+ strict: true,
48
+ onLogs: this.onPriceFeedUpdate.bind(this)
49
+ });
50
+ }
51
+ onPriceFeedUpdate(logs) {
52
+ for (const log of logs){
53
+ const priceId = (0, _utils.removeLeading0x)((0, _utils.assertDefined)(log.args.id));
54
+ const priceInfo = {
55
+ conf: (0, _utils.assertDefined)(log.args.conf).toString(),
56
+ price: (0, _utils.assertDefined)(log.args.price).toString(),
57
+ publishTime: Number((0, _utils.assertDefined)(log.args.publishTime))
58
+ };
59
+ this.logger.debug({
60
+ priceInfo
61
+ }, `Received a new Evm PriceFeedUpdate event for price feed ${this.priceIdToAlias.get(priceId)} (${priceId}).`);
62
+ this.updateLatestPriceInfo(priceId, priceInfo);
63
+ }
64
+ }
65
+ async getOnChainPriceInfo(priceId) {
66
+ let priceRaw;
67
+ try {
68
+ priceRaw = await this.pythContract.read.getPriceUnsafe([
69
+ (0, _utils.addLeading0x)(priceId)
70
+ ]);
71
+ } catch (error) {
72
+ this.logger.error(error, `Polling on-chain price for ${priceId} failed.`);
73
+ return undefined;
74
+ }
75
+ this.logger.debug(`Polled an EVM on chain price for feed ${this.priceIdToAlias.get(priceId)} (${priceId}).`);
76
+ return {
77
+ conf: priceRaw.conf,
78
+ price: priceRaw.price,
79
+ publishTime: Number(priceRaw.publishTime)
80
+ };
81
+ }
82
+ }
83
+ class EvmPricePusher {
84
+ hermesClient;
85
+ client;
86
+ pythContract;
87
+ logger;
88
+ overrideGasPriceMultiplier;
89
+ overrideGasPriceMultiplierCap;
90
+ updateFeeMultiplier;
91
+ gasLimit;
92
+ customGasStation;
93
+ gasPrice;
94
+ pusherAddress;
95
+ lastPushAttempt;
96
+ constructor(hermesClient, client, pythContract, logger, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, updateFeeMultiplier, gasLimit, customGasStation, gasPrice){
97
+ this.hermesClient = hermesClient;
98
+ this.client = client;
99
+ this.pythContract = pythContract;
100
+ this.logger = logger;
101
+ this.overrideGasPriceMultiplier = overrideGasPriceMultiplier;
102
+ this.overrideGasPriceMultiplierCap = overrideGasPriceMultiplierCap;
103
+ this.updateFeeMultiplier = updateFeeMultiplier;
104
+ this.gasLimit = gasLimit;
105
+ this.customGasStation = customGasStation;
106
+ this.gasPrice = gasPrice;
107
+ }
108
+ // The pubTimes are passed here to use the values that triggered the push.
109
+ // This is an optimization to avoid getting a newer value (as an update comes)
110
+ // and will help multiple price pushers to have consistent behaviour.
111
+ // To ensure that we transactions are landing and we are not pushing the prices twice
112
+ // we will re-use the same nonce (with a higher gas price) if the previous transaction
113
+ // is not landed yet.
114
+ async updatePriceFeed(priceIds, pubTimesToPush) {
115
+ if (priceIds.length === 0) {
116
+ return;
117
+ }
118
+ if (priceIds.length !== pubTimesToPush.length) throw new Error("Invalid arguments");
119
+ const priceFeedUpdateData = await this.getPriceFeedsUpdateData(priceIds);
120
+ const priceFeedUpdateDataWith0x = priceFeedUpdateData.map((data)=>(0, _utils.addLeading0x)(data));
121
+ let updateFee;
122
+ try {
123
+ updateFee = await this.pythContract.read.getUpdateFee([
124
+ priceFeedUpdateDataWith0x
125
+ ]);
126
+ updateFee = BigInt(Math.round(Number(updateFee) * (this.updateFeeMultiplier || 1)));
127
+ this.logger.debug(`Update fee: ${updateFee}`);
128
+ } catch (error) {
129
+ this.logger.error(error, "An unidentified error has occured when getting the update fee.");
130
+ throw error;
131
+ }
132
+ // Gas price in networks with transaction type eip1559 represents the
133
+ // addition of baseFee and priorityFee required to land the transaction. We
134
+ // are using this to remain compatible with the networks that doesn't
135
+ // support this transaction type.
136
+ let gasPrice = this.gasPrice ?? Number(await (this.customGasStation?.getCustomGasPrice() ?? this.client.getGasPrice()));
137
+ // Try to re-use the same nonce and increase the gas if the last tx is not landed yet.
138
+ this.pusherAddress ??= this.client.account.address;
139
+ const lastExecutedNonce = await this.client.getTransactionCount({
140
+ address: this.pusherAddress
141
+ }) - 1;
142
+ let gasPriceToOverride = undefined;
143
+ if (this.lastPushAttempt !== undefined) {
144
+ if (this.lastPushAttempt.nonce <= lastExecutedNonce) {
145
+ this.lastPushAttempt = undefined;
146
+ } else {
147
+ gasPriceToOverride = this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier;
148
+ }
149
+ }
150
+ if (gasPriceToOverride !== undefined && gasPriceToOverride > Number(gasPrice)) {
151
+ gasPrice = Math.min(gasPriceToOverride, gasPrice * this.overrideGasPriceMultiplierCap);
152
+ }
153
+ const txNonce = lastExecutedNonce + 1;
154
+ this.logger.debug(`Using gas price: ${gasPrice} and nonce: ${txNonce}`);
155
+ const pubTimesToPushParam = pubTimesToPush.map(BigInt);
156
+ const priceIdsWith0x = priceIds.map((priceId)=>(0, _utils.addLeading0x)(priceId));
157
+ // Update lastAttempt
158
+ this.lastPushAttempt = {
159
+ nonce: txNonce,
160
+ gasPrice: gasPrice
161
+ };
162
+ try {
163
+ const { request } = await this.pythContract.simulate.updatePriceFeedsIfNecessary([
164
+ priceFeedUpdateDataWith0x,
165
+ priceIdsWith0x,
166
+ pubTimesToPushParam
167
+ ], {
168
+ value: updateFee,
169
+ gasPrice: BigInt(Math.ceil(gasPrice)),
170
+ nonce: txNonce,
171
+ gas: this.gasLimit === undefined ? undefined : BigInt(Math.ceil(this.gasLimit))
172
+ });
173
+ this.logger.debug({
174
+ request
175
+ }, "Simulated request successfully");
176
+ const hash = await this.client.writeContract(request);
177
+ this.logger.info({
178
+ hash
179
+ }, "Price update sent");
180
+ void this.waitForTransactionReceipt(hash);
181
+ } catch (error) {
182
+ this.logger.debug({
183
+ err: error
184
+ }, "Simulating or sending transactions failed.");
185
+ if (error instanceof _viem.BaseError) {
186
+ if (error.walk((e)=>e instanceof _viem.ContractFunctionRevertedError && e.data?.errorName === "NoFreshUpdate")) {
187
+ this.logger.info("Simulation reverted because none of the updates are fresh. This is an expected behaviour to save gas. Skipping this push.");
188
+ return;
189
+ }
190
+ if (error.walk((e)=>e instanceof _viem.InsufficientFundsError)) {
191
+ this.logger.error({
192
+ err: error
193
+ }, "Wallet doesn't have enough balance. In rare cases, there might be issues with gas price " + "calculation in the RPC.");
194
+ throw error;
195
+ }
196
+ if (error.walk((e)=>e instanceof _viem.FeeCapTooLowError) || error.walk((e)=>e instanceof _viem.InternalRpcError && e.details.includes("replacement transaction underpriced"))) {
197
+ this.logger.warn("The gas price of the transaction is too low or there is an existing transaction with higher gas with the same nonce. " + "The price will be increased in the next push. Skipping this push. " + "If this keeps happening or transactions are not landing you need to increase the override gas price " + "multiplier and the cap to increase the likelihood of the transaction landing on-chain.");
198
+ return;
199
+ }
200
+ if (error.walk((e)=>e instanceof _viem.TransactionExecutionError && (e.details.includes("nonce too low") || e.message.includes("Nonce provided for the transaction")))) {
201
+ this.logger.info("The nonce is incorrect. This is an expected behaviour in high frequency or multi-instance setup. Skipping this push.");
202
+ return;
203
+ }
204
+ // Sometimes the contract function execution fails in simulation and this error is thrown.
205
+ if (error.walk((e)=>e instanceof _viem.ContractFunctionExecutionError)) {
206
+ this.logger.warn({
207
+ err: error
208
+ }, "The contract function execution failed in simulation. This is an expected behaviour in high frequency or multi-instance setup. " + "Please review this error and file an issue if it is a bug. Skipping this push.");
209
+ return;
210
+ }
211
+ // We normally crash on unknown failures but we believe that this type of error is safe to skip. The other reason is that
212
+ // wometimes we see a TransactionExecutionError because of the nonce without any details and it is not catchable.
213
+ if (error.walk((e)=>e instanceof _viem.TransactionExecutionError)) {
214
+ this.logger.error({
215
+ err: error
216
+ }, "Transaction execution failed. This is an expected behaviour in high frequency or multi-instance setup. " + "Please review this error and file an issue if it is a bug. Skipping this push.");
217
+ return;
218
+ }
219
+ // The following errors are part of the legacy code and might not work as expected.
220
+ // We are keeping them in case they help with handling what is not covered above.
221
+ if (error.message.includes("the tx doesn't have the correct nonce.") || error.message.includes("nonce too low") || error.message.includes("invalid nonce")) {
222
+ this.logger.info("The nonce is incorrect (are multiple users using this account?). Skipping this push.");
223
+ return;
224
+ }
225
+ if (error.message.includes("max fee per gas less than block base fee")) {
226
+ // We just have to handle this error and return.
227
+ // LastPushAttempt was stored with the class
228
+ // Next time the update will be executing, it will check the last attempt
229
+ // and increase the gas price accordingly.
230
+ this.logger.warn("The transaction failed with error: max fee per gas less than block base fee. " + "The fee will be increased in the next push. Skipping this push.");
231
+ return;
232
+ }
233
+ if (error.message.includes("sender doesn't have enough funds to send tx.")) {
234
+ this.logger.error("Payer is out of balance, please top it up.");
235
+ throw new Error("Please top up the wallet");
236
+ }
237
+ if (error.message.includes("could not replace existing tx")) {
238
+ this.logger.error("A transaction with the same nonce has been mined and this one is no longer needed. Skipping this push.");
239
+ return;
240
+ }
241
+ }
242
+ // If the error is not handled, we will crash the process.
243
+ this.logger.error({
244
+ err: error
245
+ }, "The transaction failed with an unhandled error. crashing the process. " + "Please review this error and file an issue if it is a bug.");
246
+ throw error;
247
+ }
248
+ }
249
+ async waitForTransactionReceipt(hash) {
250
+ try {
251
+ const receipt = await this.client.waitForTransactionReceipt({
252
+ hash: hash
253
+ });
254
+ switch(receipt.status){
255
+ case "success":
256
+ {
257
+ this.logger.debug({
258
+ hash,
259
+ receipt
260
+ }, "Price update successful");
261
+ this.logger.info({
262
+ hash
263
+ }, "Price update successful");
264
+ break;
265
+ }
266
+ default:
267
+ {
268
+ this.logger.info({
269
+ hash,
270
+ receipt
271
+ }, "Price update did not succeed or its transaction did not land. " + "This is an expected behaviour in high frequency or multi-instance setup.");
272
+ }
273
+ }
274
+ } catch (error) {
275
+ this.logger.warn({
276
+ err: error
277
+ }, "Failed to get transaction receipt");
278
+ }
279
+ }
280
+ async getPriceFeedsUpdateData(priceIds) {
281
+ const response = await this.hermesClient.getLatestPriceUpdates(priceIds, {
282
+ encoding: "hex",
283
+ ignoreInvalidPriceIds: true
284
+ });
285
+ return response.binary.data;
286
+ }
287
+ }
@@ -1,10 +1,12 @@
1
- import { IPricePusher, PriceInfo, ChainPriceListener, PriceItem } from "../interface";
2
- import { DurationInSeconds } from "../utils";
3
- import { Logger } from "pino";
4
- import { HermesClient, HexString, UnixTimestamp } from "@pythnetwork/hermes-client";
1
+ import type { HexString, UnixTimestamp } from "@pythnetwork/hermes-client";
2
+ import { HermesClient } from "@pythnetwork/hermes-client";
3
+ import type { Logger } from "pino";
4
+ import type { IPricePusher, PriceInfo, PriceItem } from "../interface.js";
5
+ import type { DurationInSeconds } from "../utils.js";
5
6
  import { CustomGasStation } from "./custom-gas-station";
6
- import { PythContract } from "./pyth-contract";
7
- import { SuperWalletClient } from "./super-wallet";
7
+ import type { PythContract } from "./pyth-contract.js";
8
+ import type { SuperWalletClient } from "./super-wallet.js";
9
+ import { ChainPriceListener } from "../interface.js";
8
10
  export declare class EvmPriceListener extends ChainPriceListener {
9
11
  private pythContract;
10
12
  private watchEvents;
@@ -35,4 +37,3 @@ export declare class EvmPricePusher implements IPricePusher {
35
37
  private waitForTransactionReceipt;
36
38
  private getPriceFeedsUpdateData;
37
39
  }
38
- //# sourceMappingURL=evm.d.ts.map