@pythnetwork/price-pusher 5.4.9 → 5.6.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.
@@ -13,6 +13,7 @@ declare const _default: {
13
13
  "custom-gas-station": Options;
14
14
  "tx-speed": Options;
15
15
  "override-gas-price-multiplier": Options;
16
+ "override-gas-price-multiplier-cap": Options;
16
17
  };
17
18
  handler: (argv: any) => void;
18
19
  };
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/evm/command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;;;oBA4CL,GAAG;;AAxC9B,wBA+GE"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/evm/command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;;;;oBAuDL,GAAG;;AAnD9B,wBA4HE"}
@@ -58,11 +58,21 @@ exports.default = {
58
58
  required: false,
59
59
  },
60
60
  "override-gas-price-multiplier": {
61
- description: "Multiply the gas price by this number if the transaction is not landing to override it. Default to 1.1",
61
+ description: "Multiply the previous gas price by this number if the transaction is not landing to override. " +
62
+ "Please note that the gas price can grow exponentially on consecutive failures; " +
63
+ "to set a cap on the multiplier, use the `override-gas-price-multiplier-cap` option." +
64
+ "Default to 1.1",
62
65
  type: "number",
63
66
  required: false,
64
67
  default: 1.1,
65
68
  },
69
+ "override-gas-price-multiplier-cap": {
70
+ description: "Maximum gas price multiplier to use in override compared to the RPC returned " +
71
+ "gas price. Default to 5",
72
+ type: "number",
73
+ required: false,
74
+ default: 5,
75
+ },
66
76
  ...options.priceConfigFile,
67
77
  ...options.priceServiceEndpoint,
68
78
  ...options.mnemonicFile,
@@ -72,7 +82,7 @@ exports.default = {
72
82
  },
73
83
  handler: function (argv) {
74
84
  // FIXME: type checks for this
75
- const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pythContractAddress, pushingFrequency, pollingFrequency, customGasStation, txSpeed, overrideGasPriceMultiplier, } = argv;
85
+ const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pythContractAddress, pushingFrequency, pollingFrequency, customGasStation, txSpeed, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, } = argv;
76
86
  const priceConfigs = (0, price_config_1.readPriceConfigFile)(priceConfigFile);
77
87
  const priceServiceConnection = new price_service_client_1.PriceServiceConnection(priceServiceEndpoint, {
78
88
  logger: {
@@ -95,7 +105,7 @@ exports.default = {
95
105
  pollingFrequency,
96
106
  });
97
107
  const gasStation = (0, custom_gas_station_1.getCustomGasStation)(customGasStation, txSpeed);
98
- const evmPusher = new evm_1.EvmPricePusher(priceServiceConnection, pythContractFactory, overrideGasPriceMultiplier, gasStation);
108
+ const evmPusher = new evm_1.EvmPricePusher(priceServiceConnection, pythContractFactory, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, gasStation);
99
109
  const controller = new controller_1.Controller(priceConfigs, pythListener, evmListener, evmPusher, { pushingFrequency });
100
110
  controller.start();
101
111
  },
package/lib/evm/evm.d.ts CHANGED
@@ -18,12 +18,13 @@ export declare class EvmPriceListener extends ChainPriceListener {
18
18
  export declare class EvmPricePusher implements IPricePusher {
19
19
  private connection;
20
20
  private overrideGasPriceMultiplier;
21
+ private overrideGasPriceMultiplierCap;
21
22
  private customGasStation?;
22
23
  private pythContract;
23
24
  private web3;
24
25
  private pusherAddress;
25
26
  private lastPushAttempt;
26
- constructor(connection: PriceServiceConnection, pythContractFactory: PythContractFactory, overrideGasPriceMultiplier: number, customGasStation?: CustomGasStation);
27
+ constructor(connection: PriceServiceConnection, pythContractFactory: PythContractFactory, overrideGasPriceMultiplier: number, overrideGasPriceMultiplierCap: number, customGasStation?: CustomGasStation);
27
28
  updatePriceFeed(priceIds: string[], pubTimesToPush: UnixTimestamp[]): Promise<void>;
28
29
  private getPriceFeedsUpdateData;
29
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"evm.d.ts","sourceRoot":"","sources":["../../src/evm/evm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAa,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAgB,iBAAiB,EAAmB,MAAM,UAAU,CAAC;AAE5E,OAAO,gBAAgB,MAAM,4BAA4B,CAAC;AAG1D,OAAO,EACL,sBAAsB,EACtB,SAAS,EACT,aAAa,EACd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAIxD,qBAAa,gBAAiB,SAAQ,kBAAkB;IACtD,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,YAAY,CAAW;gBAG7B,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,SAAS,EAAE,EACvB,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAUG,KAAK;YAeG,iBAAiB;IAc/B,OAAO,CAAC,iBAAiB;IAsBnB,mBAAmB,CACvB,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAwBlC;AAED,qBAAa,cAAe,YAAW,YAAY;IAQ/C,OAAO,CAAC,UAAU;IAElB,OAAO,CAAC,0BAA0B;IATpC,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,YAAY,CAAW;IAC/B,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,eAAe,CAA0B;gBAGvC,UAAU,EAAE,sBAAsB,EAC1C,mBAAmB,EAAE,mBAAmB,EAChC,0BAA0B,EAAE,MAAM,EAC1C,gBAAgB,CAAC,EAAE,gBAAgB;IAa/B,eAAe,CACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,aAAa,EAAE,GAC9B,OAAO,CAAC,IAAI,CAAC;YA6IF,uBAAuB;CAQtC;AAED,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,mBAAmB;gBAFnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM;IAGrC;;;;;;OAMG;IACH,2BAA2B,IAAI,QAAQ;IAcvC;;;;;OAKG;IACH,kBAAkB,IAAI,QAAQ;IAS9B,oBAAoB,IAAI,OAAO;IAI/B,kBAAkB;IA0BlB,uBAAuB;CAQxB"}
1
+ {"version":3,"file":"evm.d.ts","sourceRoot":"","sources":["../../src/evm/evm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAa,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAgB,iBAAiB,EAAmB,MAAM,UAAU,CAAC;AAE5E,OAAO,gBAAgB,MAAM,4BAA4B,CAAC;AAG1D,OAAO,EACL,sBAAsB,EACtB,SAAS,EACT,aAAa,EACd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAIxD,qBAAa,gBAAiB,SAAQ,kBAAkB;IACtD,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,YAAY,CAAW;gBAG7B,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,SAAS,EAAE,EACvB,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAUG,KAAK;YAeG,iBAAiB;IAc/B,OAAO,CAAC,iBAAiB;IAsBnB,mBAAmB,CACvB,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAwBlC;AAED,qBAAa,cAAe,YAAW,YAAY;IAQ/C,OAAO,CAAC,UAAU;IAElB,OAAO,CAAC,0BAA0B;IAClC,OAAO,CAAC,6BAA6B;IAVvC,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,YAAY,CAAW;IAC/B,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,eAAe,CAA0B;gBAGvC,UAAU,EAAE,sBAAsB,EAC1C,mBAAmB,EAAE,mBAAmB,EAChC,0BAA0B,EAAE,MAAM,EAClC,6BAA6B,EAAE,MAAM,EAC7C,gBAAgB,CAAC,EAAE,gBAAgB;IAa/B,eAAe,CACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,aAAa,EAAE,GAC9B,OAAO,CAAC,IAAI,CAAC;YAgJF,uBAAuB;CAQtC;AAED,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,mBAAmB;gBAFnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM;IAGrC;;;;;;OAMG;IACH,2BAA2B,IAAI,QAAQ;IAcvC;;;;;OAKG;IACH,kBAAkB,IAAI,QAAQ;IAS9B,oBAAoB,IAAI,OAAO;IAI/B,kBAAkB;IA0BlB,uBAAuB;CAQxB"}
package/lib/evm/evm.js CHANGED
@@ -80,14 +80,16 @@ exports.EvmPriceListener = EvmPriceListener;
80
80
  class EvmPricePusher {
81
81
  connection;
82
82
  overrideGasPriceMultiplier;
83
+ overrideGasPriceMultiplierCap;
83
84
  customGasStation;
84
85
  pythContract;
85
86
  web3;
86
87
  pusherAddress;
87
88
  lastPushAttempt;
88
- constructor(connection, pythContractFactory, overrideGasPriceMultiplier, customGasStation) {
89
+ constructor(connection, pythContractFactory, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, customGasStation) {
89
90
  this.connection = connection;
90
91
  this.overrideGasPriceMultiplier = overrideGasPriceMultiplier;
92
+ this.overrideGasPriceMultiplierCap = overrideGasPriceMultiplierCap;
91
93
  this.customGasStation = customGasStation;
92
94
  this.pythContract = pythContractFactory.createPythContractWithPayer();
93
95
  this.web3 = new web3_1.default(pythContractFactory.createWeb3PayerProvider());
@@ -135,7 +137,7 @@ class EvmPricePusher {
135
137
  }
136
138
  }
137
139
  if (gasPriceToOverride !== undefined && gasPriceToOverride > gasPrice) {
138
- gasPrice = gasPriceToOverride;
140
+ gasPrice = Math.min(gasPriceToOverride, gasPrice * this.overrideGasPriceMultiplierCap);
139
141
  }
140
142
  const txNonce = lastExecutedNonce + 1;
141
143
  console.log(`Using gas price: ${gasPrice} and nonce: ${txNonce}`);
@@ -9,12 +9,8 @@ declare const _default: {
9
9
  "price-service-endpoint": Options;
10
10
  "price-config-file": Options;
11
11
  endpoint: Options;
12
- "pyth-package-id": Options;
13
12
  "pyth-state-id": Options;
14
- "wormhole-package-id": Options;
15
13
  "wormhole-state-id": Options;
16
- "price-feed-to-price-info-object-table-id": Options;
17
- "max-vaas-per-ptb": Options;
18
14
  "num-gas-objects": Options;
19
15
  "gas-budget": Options;
20
16
  };
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/sui/command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;oBAgFC,GAAG;;AA5EpC,wBA8JE"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/sui/command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;;;oBAoDC,GAAG;;AAhDpC,wBA0HE"}
@@ -48,42 +48,18 @@ exports.default = {
48
48
  type: "string",
49
49
  required: true,
50
50
  },
51
- "pyth-package-id": {
52
- description: "Pyth Package Id. Can be found here" +
53
- "https://docs.pyth.network/documentation/pythnet-price-feeds/sui",
54
- type: "string",
55
- required: true,
56
- },
57
51
  "pyth-state-id": {
58
52
  description: "Pyth State Id. Can be found here" +
59
53
  "https://docs.pyth.network/documentation/pythnet-price-feeds/sui",
60
54
  type: "string",
61
55
  required: true,
62
56
  },
63
- "wormhole-package-id": {
64
- description: "Wormhole Package Id. Can be found here" +
65
- "https://docs.pyth.network/documentation/pythnet-price-feeds/sui",
66
- type: "string",
67
- required: true,
68
- },
69
57
  "wormhole-state-id": {
70
58
  description: "Wormhole State Id. Can be found here" +
71
59
  "https://docs.pyth.network/documentation/pythnet-price-feeds/sui",
72
60
  type: "string",
73
61
  required: true,
74
62
  },
75
- "price-feed-to-price-info-object-table-id": {
76
- description: "This is the id of the table which stored the information related to price data. You can find it here: " +
77
- "https://docs.pyth.network/documentation/pythnet-price-feeds/sui",
78
- type: "string",
79
- required: true,
80
- },
81
- "max-vaas-per-ptb": {
82
- description: "Maximum number of VAAs that can be included in a single PTB.",
83
- type: "number",
84
- required: true,
85
- default: 1,
86
- },
87
63
  "num-gas-objects": {
88
64
  description: "Number of gas objects in the pool.",
89
65
  type: "number",
@@ -103,7 +79,7 @@ exports.default = {
103
79
  ...options.pushingFrequency,
104
80
  },
105
81
  handler: async function (argv) {
106
- const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pushingFrequency, pollingFrequency, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, numGasObjects, gasBudget, } = argv;
82
+ const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pushingFrequency, pollingFrequency, pythStateId, wormholeStateId, numGasObjects, gasBudget, } = argv;
107
83
  const priceConfigs = (0, price_config_1.readPriceConfigFile)(priceConfigFile);
108
84
  const priceServiceConnection = new price_service_client_1.PriceServiceConnection(priceServiceEndpoint, {
109
85
  logger: {
@@ -124,8 +100,8 @@ exports.default = {
124
100
  .toSuiAddress()}`);
125
101
  const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
126
102
  const pythListener = new pyth_price_listener_1.PythPriceListener(priceServiceConnection, priceItems);
127
- const suiListener = new sui_1.SuiPriceListener(pythPackageId, priceFeedToPriceInfoObjectTableId, endpoint, priceItems, { pollingFrequency });
128
- const suiPusher = await sui_1.SuiPricePusher.createWithAutomaticGasPool(priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, numGasObjects);
103
+ const suiListener = new sui_1.SuiPriceListener(pythStateId, wormholeStateId, endpoint, priceItems, { pollingFrequency });
104
+ const suiPusher = await sui_1.SuiPricePusher.createWithAutomaticGasPool(priceServiceConnection, pythStateId, wormholeStateId, endpoint, mnemonic, gasBudget, numGasObjects);
129
105
  const controller = new controller_1.Controller(priceConfigs, pythListener, suiListener, suiPusher, { pushingFrequency });
130
106
  controller.start();
131
107
  },
package/lib/sui/sui.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { ChainPriceListener, IPricePusher, PriceInfo, PriceItem } from "../interface";
2
2
  import { DurationInSeconds } from "../utils";
3
3
  import { PriceServiceConnection } from "@pythnetwork/price-service-client";
4
- import { RawSigner, SuiObjectRef } from "@mysten/sui.js";
4
+ import { JsonRpcProvider, RawSigner, SuiObjectRef, ObjectId } from "@mysten/sui.js";
5
+ import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
5
6
  export declare class SuiPriceListener extends ChainPriceListener {
6
- private pythPackageId;
7
- private priceFeedToPriceInfoObjectTableId;
8
- private endpoint;
9
- constructor(pythPackageId: string, priceFeedToPriceInfoObjectTableId: string, endpoint: string, priceItems: PriceItem[], config: {
7
+ private pythClient;
8
+ private provider;
9
+ constructor(pythStateId: ObjectId, wormholeStateId: ObjectId, endpoint: string, priceItems: PriceItem[], config: {
10
10
  pollingFrequency: DurationInSeconds;
11
11
  });
12
12
  getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined>;
@@ -32,18 +32,24 @@ export declare class SuiPricePusher implements IPricePusher {
32
32
  private pythStateId;
33
33
  private wormholePackageId;
34
34
  private wormholeStateId;
35
- private priceFeedToPriceInfoObjectTableId;
36
- private maxVaasPerPtb;
37
35
  private gasBudget;
38
36
  private gasPool;
39
- constructor(signer: RawSigner, priceServiceConnection: PriceServiceConnection, pythPackageId: string, pythStateId: string, wormholePackageId: string, wormholeStateId: string, priceFeedToPriceInfoObjectTableId: string, maxVaasPerPtb: number, endpoint: string, mnemonic: string, gasBudget: number, gasPool: SuiObjectRef[]);
37
+ private pythClient;
38
+ constructor(signer: RawSigner, priceServiceConnection: PriceServiceConnection, pythPackageId: string, pythStateId: string, wormholePackageId: string, wormholeStateId: string, endpoint: string, mnemonic: string, gasBudget: number, gasPool: SuiObjectRef[], pythClient: SuiPythClient);
39
+ /**
40
+ * getPackageId returns the latest package id that the object belongs to. Use this to
41
+ * fetch the latest package id for a given object id and handle package upgrades automatically.
42
+ * @param provider
43
+ * @param objectId
44
+ * @returns package id
45
+ */
46
+ static getPackageId(provider: JsonRpcProvider, objectId: ObjectId): Promise<ObjectId>;
40
47
  /**
41
48
  * Create a price pusher with a pool of `numGasObjects` gas coins that will be used to send transactions.
42
49
  * The gas coins of the wallet for the provided mnemonic will be merged and then evenly split into `numGasObjects`.
43
50
  */
44
- static createWithAutomaticGasPool(priceServiceConnection: PriceServiceConnection, pythPackageId: string, pythStateId: string, wormholePackageId: string, wormholeStateId: string, priceFeedToPriceInfoObjectTableId: string, maxVaasPerPtb: number, endpoint: string, mnemonic: string, gasBudget: number, numGasObjects: number): Promise<SuiPricePusher>;
51
+ static createWithAutomaticGasPool(priceServiceConnection: PriceServiceConnection, pythStateId: string, wormholeStateId: string, endpoint: string, mnemonic: string, gasBudget: number, numGasObjects: number): Promise<SuiPricePusher>;
45
52
  updatePriceFeed(priceIds: string[], pubTimesToPush: number[]): Promise<void>;
46
- private createPriceUpdateTransaction;
47
53
  /** Send every transaction in txs in parallel, returning when all transactions have completed. */
48
54
  private sendTransactionBlocks;
49
55
  /** Send a single transaction block using a gas coin from the pool. */
@@ -1 +1 @@
1
- {"version":3,"file":"sui.d.ts","sourceRoot":"","sources":["../../src/sui/sui.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAIL,SAAS,EAIT,YAAY,EAKb,MAAM,gBAAgB,CAAC;AAMxB,qBAAa,gBAAiB,SAAQ,kBAAkB;IAEpD,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,iCAAiC;IACzC,OAAO,CAAC,QAAQ;gBAFR,aAAa,EAAE,MAAM,EACrB,iCAAiC,EAAE,MAAM,EACzC,QAAQ,EAAE,MAAM,EACxB,UAAU,EAAE,SAAS,EAAE,EACvB,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAKG,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAmD3E;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,cAAe,YAAW,YAAY;IAE/C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,iCAAiC;IACzC,OAAO,CAAC,aAAa;IAGrB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,OAAO;gBAXE,MAAM,EAAE,SAAS,EAC1B,sBAAsB,EAAE,sBAAsB,EAC9C,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,eAAe,EAAE,MAAM,EACvB,iCAAiC,EAAE,MAAM,EACzC,aAAa,EAAE,MAAM,EAC7B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EACR,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,YAAY,EAAE;IAGjC;;;OAGG;WACU,0BAA0B,CACrC,sBAAsB,EAAE,sBAAsB,EAC9C,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,eAAe,EAAE,MAAM,EACvB,iCAAiC,EAAE,MAAM,EACzC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,cAAc,CAAC;IAiCpB,eAAe,CACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAAC,IAAI,CAAC;YAsDF,4BAA4B;IA2E1C,iGAAiG;YACnF,qBAAqB;IAMnC,sEAAsE;YACxD,oBAAoB;mBAsDb,iBAAiB;mBA8BjB,yBAAyB;mBAoBzB,cAAc;mBAiCd,mBAAmB;mBAsCnB,oBAAoB;CA2C1C"}
1
+ {"version":3,"file":"sui.d.ts","sourceRoot":"","sources":["../../src/sui/sui.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EACL,eAAe,EAGf,SAAS,EAGT,YAAY,EAKZ,QAAQ,EACT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAOzD,qBAAa,gBAAiB,SAAQ,kBAAkB;IACtD,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,QAAQ,CAAkB;gBAGhC,WAAW,EAAE,QAAQ,EACrB,eAAe,EAAE,QAAQ,EACzB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,SAAS,EAAE,EACvB,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAWG,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CA+C3E;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,cAAe,YAAW,YAAY;IAE/C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,eAAe;IAGvB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;gBAVD,MAAM,EAAE,SAAS,EAC1B,sBAAsB,EAAE,sBAAsB,EAC9C,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,eAAe,EAAE,MAAM,EAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EACR,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,YAAY,EAAE,EACvB,UAAU,EAAE,aAAa;IAGnC;;;;;;OAMG;WACU,YAAY,CACvB,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,QAAQ,CAAC;IAuBpB;;;OAGG;WACU,0BAA0B,CACrC,sBAAsB,EAAE,sBAAsB,EAC9C,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,cAAc,CAAC;IAiDpB,eAAe,CACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAAC,IAAI,CAAC;IA0ChB,iGAAiG;YACnF,qBAAqB;IAMnC,sEAAsE;YACxD,oBAAoB;mBAsDb,iBAAiB;mBAuCjB,yBAAyB;mBAoBzB,cAAc;mBAiCd,mBAAmB;mBAsCnB,oBAAoB;CA+D1C"}
package/lib/sui/sui.js CHANGED
@@ -3,26 +3,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SuiPricePusher = exports.SuiPriceListener = void 0;
4
4
  const interface_1 = require("../interface");
5
5
  const sui_js_1 = require("@mysten/sui.js");
6
+ const pyth_sui_js_1 = require("@pythnetwork/pyth-sui-js");
6
7
  const GAS_FEE_FOR_SPLIT = 2_000_000_000;
7
8
  // TODO: read this from on chain config
8
9
  const MAX_NUM_GAS_OBJECTS_IN_PTB = 256;
9
10
  const MAX_NUM_OBJECTS_IN_ARGUMENT = 510;
10
11
  class SuiPriceListener extends interface_1.ChainPriceListener {
11
- pythPackageId;
12
- priceFeedToPriceInfoObjectTableId;
13
- endpoint;
14
- constructor(pythPackageId, priceFeedToPriceInfoObjectTableId, endpoint, priceItems, config) {
12
+ pythClient;
13
+ provider;
14
+ constructor(pythStateId, wormholeStateId, endpoint, priceItems, config) {
15
15
  super("sui", config.pollingFrequency, priceItems);
16
- this.pythPackageId = pythPackageId;
17
- this.priceFeedToPriceInfoObjectTableId = priceFeedToPriceInfoObjectTableId;
18
- this.endpoint = endpoint;
16
+ this.provider = new sui_js_1.JsonRpcProvider(new sui_js_1.Connection({ fullnode: endpoint }));
17
+ this.pythClient = new pyth_sui_js_1.SuiPythClient(this.provider, pythStateId, wormholeStateId);
19
18
  }
20
19
  async getOnChainPriceInfo(priceId) {
21
20
  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);
21
+ const priceInfoObjectId = await this.pythClient.getPriceFeedObjectId(priceId);
22
+ if (priceInfoObjectId === undefined) {
23
+ throw new Error("Price not found on chain for price id " + priceId);
24
+ }
24
25
  // Fetching the price info object for the above priceInfoObjectId
25
- const priceInfoObject = await provider.getObject({
26
+ const priceInfoObject = await this.provider.getObject({
26
27
  id: priceInfoObjectId,
27
28
  options: { showContent: true },
28
29
  });
@@ -72,33 +73,61 @@ class SuiPricePusher {
72
73
  pythStateId;
73
74
  wormholePackageId;
74
75
  wormholeStateId;
75
- priceFeedToPriceInfoObjectTableId;
76
- maxVaasPerPtb;
77
76
  gasBudget;
78
77
  gasPool;
79
- constructor(signer, priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, gasPool) {
78
+ pythClient;
79
+ constructor(signer, priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, endpoint, mnemonic, gasBudget, gasPool, pythClient) {
80
80
  this.signer = signer;
81
81
  this.priceServiceConnection = priceServiceConnection;
82
82
  this.pythPackageId = pythPackageId;
83
83
  this.pythStateId = pythStateId;
84
84
  this.wormholePackageId = wormholePackageId;
85
85
  this.wormholeStateId = wormholeStateId;
86
- this.priceFeedToPriceInfoObjectTableId = priceFeedToPriceInfoObjectTableId;
87
- this.maxVaasPerPtb = maxVaasPerPtb;
88
86
  this.gasBudget = gasBudget;
89
87
  this.gasPool = gasPool;
88
+ this.pythClient = pythClient;
89
+ }
90
+ /**
91
+ * getPackageId returns the latest package id that the object belongs to. Use this to
92
+ * fetch the latest package id for a given object id and handle package upgrades automatically.
93
+ * @param provider
94
+ * @param objectId
95
+ * @returns package id
96
+ */
97
+ static async getPackageId(provider, objectId) {
98
+ const state = await provider
99
+ .getObject({
100
+ id: objectId,
101
+ options: {
102
+ showContent: true,
103
+ },
104
+ })
105
+ .then((result) => {
106
+ if (result.data?.content?.dataType == "moveObject") {
107
+ return result.data.content.fields;
108
+ }
109
+ throw new Error("not move object");
110
+ });
111
+ if ("upgrade_cap" in state) {
112
+ return state.upgrade_cap.fields.package;
113
+ }
114
+ throw new Error("upgrade_cap not found");
90
115
  }
91
116
  /**
92
117
  * Create a price pusher with a pool of `numGasObjects` gas coins that will be used to send transactions.
93
118
  * The gas coins of the wallet for the provided mnemonic will be merged and then evenly split into `numGasObjects`.
94
119
  */
95
- static async createWithAutomaticGasPool(priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, numGasObjects) {
120
+ static async createWithAutomaticGasPool(priceServiceConnection, pythStateId, wormholeStateId, endpoint, mnemonic, gasBudget, numGasObjects) {
96
121
  if (numGasObjects > MAX_NUM_OBJECTS_IN_ARGUMENT) {
97
122
  throw new Error(`numGasObjects cannot be greater than ${MAX_NUM_OBJECTS_IN_ARGUMENT} until we implement split chunking`);
98
123
  }
99
- 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 })));
124
+ const provider = new sui_js_1.JsonRpcProvider(new sui_js_1.Connection({ fullnode: endpoint }));
125
+ const signer = new sui_js_1.RawSigner(sui_js_1.Ed25519Keypair.deriveKeypair(mnemonic), provider);
126
+ const pythPackageId = await SuiPricePusher.getPackageId(provider, pythStateId);
127
+ const wormholePackageId = await SuiPricePusher.getPackageId(provider, wormholeStateId);
100
128
  const gasPool = await SuiPricePusher.initializeGasPool(signer, numGasObjects);
101
- return new SuiPricePusher(signer, priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, gasPool);
129
+ const pythClient = new pyth_sui_js_1.SuiPythClient(provider, pythStateId, wormholeStateId);
130
+ return new SuiPricePusher(signer, priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, endpoint, mnemonic, gasBudget, gasPool, pythClient);
102
131
  }
103
132
  async updatePriceFeed(priceIds, pubTimesToPush) {
104
133
  if (priceIds.length === 0) {
@@ -110,99 +139,20 @@ class SuiPricePusher {
110
139
  console.warn("Skipping update: no available gas coin.");
111
140
  return;
112
141
  }
113
- const priceFeeds = await this.priceServiceConnection.getLatestPriceFeeds(priceIds);
114
- if (priceFeeds === undefined) {
115
- console.warn("Failed to fetch price updates. Skipping push.");
116
- return;
117
- }
118
- const vaaToPriceFeedIds = new Map();
119
- for (const priceFeed of priceFeeds) {
120
- // The ! will succeed as long as the priceServiceConnection is configured to return binary vaa data (which it is).
121
- const vaa = priceFeed.getVAA();
122
- if (!vaaToPriceFeedIds.has(vaa)) {
123
- vaaToPriceFeedIds.set(vaa, []);
124
- }
125
- vaaToPriceFeedIds.get(vaa).push(priceFeed.id);
126
- }
127
- const txs = [];
128
- let currentBatchVaas = [];
129
- let currentBatchPriceFeedIds = [];
130
- for (const [vaa, priceFeedIds] of vaaToPriceFeedIds.entries()) {
131
- currentBatchVaas.push(vaa);
132
- currentBatchPriceFeedIds.push(...priceFeedIds);
133
- if (currentBatchVaas.length >= this.maxVaasPerPtb) {
134
- const tx = await this.createPriceUpdateTransaction(currentBatchVaas, currentBatchPriceFeedIds);
135
- if (tx !== undefined) {
136
- txs.push(tx);
137
- }
138
- currentBatchVaas = [];
139
- currentBatchPriceFeedIds = [];
140
- }
141
- }
142
- await this.sendTransactionBlocks(txs);
143
- }
144
- async createPriceUpdateTransaction(vaas, priceIds) {
145
- const tx = new sui_js_1.TransactionBlock();
146
- // Parse our batch price attestation VAA bytes using Wormhole.
147
- // Check out the Wormhole cross-chain bridge and generic messaging protocol here:
148
- // https://github.com/wormhole-foundation/wormhole
149
- let verified_vaas = [];
150
- for (const vaa of vaas) {
151
- const [verified_vaa] = tx.moveCall({
152
- target: `${this.wormholePackageId}::vaa::parse_and_verify`,
153
- arguments: [
154
- tx.object(this.wormholeStateId),
155
- tx.pure([...Buffer.from(vaa, "base64")]),
156
- tx.object(sui_js_1.SUI_CLOCK_OBJECT_ID),
157
- ],
158
- });
159
- verified_vaas = verified_vaas.concat(verified_vaa);
160
- }
161
- // Create a hot potato vector of price feed updates that will
162
- // be used to update price feeds.
163
- let [price_updates_hot_potato] = tx.moveCall({
164
- target: `${this.pythPackageId}::pyth::create_price_infos_hot_potato`,
165
- arguments: [
166
- tx.object(this.pythStateId),
167
- tx.makeMoveVec({
168
- type: `${this.wormholePackageId}::vaa::VAA`,
169
- objects: verified_vaas,
170
- }),
171
- tx.object(sui_js_1.SUI_CLOCK_OBJECT_ID),
172
- ],
173
- });
174
- // Update each price info object (containing our price feeds of interest)
175
- // using the hot potato vector.
176
- for (const priceId of priceIds) {
177
- let priceInfoObjectId;
178
- try {
179
- priceInfoObjectId = await priceIdToPriceInfoObjectId(this.signer.provider, this.pythPackageId, this.priceFeedToPriceInfoObjectTableId, priceId);
180
- }
181
- catch (e) {
182
- console.log("Error fetching price info object id for ", priceId);
183
- console.error(e);
184
- return undefined;
142
+ // 3 price feeds per transaction is the optimal number for gas cost.
143
+ const priceIdChunks = chunkArray(priceIds, 3);
144
+ const txBlocks = [];
145
+ await Promise.all(priceIdChunks.map(async (priceIdChunk) => {
146
+ const vaas = await this.priceServiceConnection.getLatestVaas(priceIdChunk);
147
+ if (vaas.length !== 1) {
148
+ throw new Error(`Expected a single VAA for all priceIds ${priceIdChunk} but received ${vaas.length} VAAs: ${vaas}`);
185
149
  }
186
- const coin = tx.splitCoins(tx.gas, [tx.pure(1)]);
187
- [price_updates_hot_potato] = tx.moveCall({
188
- target: `${this.pythPackageId}::pyth::update_single_price_feed`,
189
- arguments: [
190
- tx.object(this.pythStateId),
191
- price_updates_hot_potato,
192
- tx.object(priceInfoObjectId),
193
- coin,
194
- tx.object(sui_js_1.SUI_CLOCK_OBJECT_ID),
195
- ],
196
- });
197
- }
198
- // Explicitly destroy the hot potato vector, since it can't be dropped
199
- // automatically.
200
- tx.moveCall({
201
- target: `${this.pythPackageId}::hot_potato_vector::destroy`,
202
- arguments: [price_updates_hot_potato],
203
- typeArguments: [`${this.pythPackageId}::price_info::PriceInfo`],
204
- });
205
- return tx;
150
+ const vaa = vaas[0];
151
+ const tx = new sui_js_1.TransactionBlock();
152
+ await this.pythClient.updatePriceFeeds(tx, [Buffer.from(vaa, "base64")], priceIdChunk);
153
+ txBlocks.push(tx);
154
+ }));
155
+ await this.sendTransactionBlocks(txBlocks);
206
156
  }
207
157
  /** Send every transaction in txs in parallel, returning when all transactions have completed. */
208
158
  async sendTransactionBlocks(txs) {
@@ -253,11 +203,20 @@ class SuiPricePusher {
253
203
  // split them equally into numGasObjects.
254
204
  static async initializeGasPool(signer, numGasObjects) {
255
205
  const signerAddress = await signer.getAddress();
256
- const { totalBalance: balance } = await signer.provider.getBalance({
257
- owner: signerAddress,
206
+ const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne(signer, signerAddress);
207
+ const coinResult = await signer.provider.getObject({
208
+ id: consolidatedCoin.objectId,
209
+ options: { showContent: true },
258
210
  });
211
+ let balance;
212
+ if (coinResult.data &&
213
+ coinResult.data.content &&
214
+ coinResult.data.content.dataType == "moveObject") {
215
+ balance = coinResult.data.content.fields.balance;
216
+ }
217
+ else
218
+ throw new Error("Bad coin object");
259
219
  const splitAmount = (BigInt(balance) - BigInt(GAS_FEE_FOR_SPLIT)) / BigInt(numGasObjects);
260
- const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne(signer, signerAddress);
261
220
  const gasPool = await SuiPricePusher.splitGasCoinEqually(signer, signerAddress, Number(splitAmount), numGasObjects, consolidatedCoin);
262
221
  console.log("Gas pool is filled with coins: ", gasPool);
263
222
  return gasPool;
@@ -335,17 +294,35 @@ class SuiPricePusher {
335
294
  }
336
295
  const gasCoinsChunks = chunkArray(gasCoins, MAX_NUM_GAS_OBJECTS_IN_PTB - 2);
337
296
  let finalCoin;
297
+ const lockedAddresses = new Set();
338
298
  for (let i = 0; i < gasCoinsChunks.length; i++) {
339
299
  const mergeTx = new sui_js_1.TransactionBlock();
340
300
  let coins = gasCoinsChunks[i];
301
+ coins = coins.filter((coin) => !lockedAddresses.has(coin.objectId));
341
302
  if (finalCoin) {
342
303
  coins = [finalCoin, ...coins];
343
304
  }
344
305
  mergeTx.setGasPayment(coins);
345
- const mergeResult = await signer.signAndExecuteTransactionBlock({
346
- transactionBlock: mergeTx,
347
- options: { showEffects: true },
348
- });
306
+ let mergeResult;
307
+ try {
308
+ mergeResult = await signer.signAndExecuteTransactionBlock({
309
+ transactionBlock: mergeTx,
310
+ options: { showEffects: true },
311
+ });
312
+ }
313
+ catch (e) {
314
+ if (String(e).includes("quorum of validators because of locked objects. Retried a conflicting transaction")) {
315
+ Object.values(e.data).forEach((lockedObjects) => {
316
+ lockedObjects.forEach((lockedObject) => {
317
+ lockedAddresses.add(lockedObject[0]);
318
+ });
319
+ });
320
+ // retry merging without the locked coins
321
+ i--;
322
+ continue;
323
+ }
324
+ throw e;
325
+ }
349
326
  const error = (0, sui_js_1.getExecutionStatusError)(mergeResult);
350
327
  if (error) {
351
328
  throw new Error(`Failed to merge coins when initializing gas pool: ${error}. Try re-running the script`);
@@ -356,37 +333,6 @@ class SuiPricePusher {
356
333
  }
357
334
  }
358
335
  exports.SuiPricePusher = SuiPricePusher;
359
- // We are calculating stored price info object id for given price id
360
- // The mapping between which is static. Hence, we are caching it here.
361
- const CACHE = {};
362
- // For given priceid, this method will fetch the price info object id
363
- // where the price information for the corresponding price feed is stored
364
- async function priceIdToPriceInfoObjectId(provider, pythPackageId, priceFeedToPriceInfoObjectTableId, priceId) {
365
- // Check if this was fetched before.
366
- if (CACHE[priceId] !== undefined)
367
- return CACHE[priceId];
368
- const storedObjectID = await provider.getDynamicFieldObject({
369
- parentId: priceFeedToPriceInfoObjectTableId,
370
- name: {
371
- type: `${pythPackageId}::price_identifier::PriceIdentifier`,
372
- value: {
373
- bytes: "0x" + priceId,
374
- },
375
- },
376
- });
377
- if (storedObjectID.error !== undefined)
378
- throw storedObjectID.error;
379
- if (storedObjectID.data === undefined ||
380
- storedObjectID.data.content === undefined)
381
- throw new Error("Price not found on chain for price id " + priceId);
382
- if (storedObjectID.data.content.dataType !== "moveObject")
383
- throw new Error("fetched object datatype should be moveObject");
384
- // This ID points to the price info object for the given price id stored on chain
385
- const priceInfoObjectId = storedObjectID.data.content.fields.value;
386
- // cache the price info object id
387
- CACHE[priceId] = priceInfoObjectId;
388
- return priceInfoObjectId;
389
- }
390
336
  function chunkArray(array, size) {
391
337
  const chunked = [];
392
338
  let index = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pythnetwork/price-pusher",
3
- "version": "5.4.9",
3
+ "version": "5.6.2",
4
4
  "description": "Pyth Price Pusher",
5
5
  "homepage": "https://pyth.network",
6
6
  "main": "lib/index.js",
@@ -52,9 +52,10 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@injectivelabs/sdk-ts": "1.10.72",
55
- "@mysten/sui.js": "^0.34.0",
55
+ "@mysten/sui.js": "^0.37.1",
56
56
  "@pythnetwork/price-service-client": "*",
57
57
  "@pythnetwork/pyth-sdk-solidity": "*",
58
+ "@pythnetwork/pyth-sui-js": "*",
58
59
  "@truffle/hdwallet-provider": "^2.1.3",
59
60
  "aptos": "^1.8.5",
60
61
  "joi": "^17.6.0",
@@ -63,5 +64,5 @@
63
64
  "yaml": "^2.1.1",
64
65
  "yargs": "^17.5.1"
65
66
  },
66
- "gitHead": "d5c3090442940a6a1d7c254be9896fa38b827e40"
67
+ "gitHead": "eb9526675cc3aa5f6dd99e43986ea767128bfd4f"
67
68
  }