# Pyth price pusher

Pyth price pusher is a service that regularly pushes updates to the on-chain Pyth price based on configurable conditions.

## Background

Pyth is a cross-chain oracle that streams price updates over the peer-to-peer [Wormhole Network](https://wormholenetwork.com/).
These price updates can be consumed on any chain that has a deployment of the Pyth contract.
By default, Pyth does not automatically update the on-chain price every time the off-chain price changes;
instead, anyone can permissionlessly update the on-chain price prior to using it.
For more information please refer to [this document](https://docs.pyth.network/documentation/how-pyth-works).

Protocols integrating with can update the on-chain Pyth prices in two different ways.
The first approach is on-demand updates: package a Pyth price update together with each transaction that depends on it.
On-demand updates minimize latency and are more gas efficient, as prices are only updated on-chain when they are needed.

The second approach is to run this service to regularly push updates to the on-chain price.
This approach is useful for protocols that already depend on regular push updates.

## Running Price Pusher

The price pusher service monitors both the off-chain and on-chain Pyth price for a configured set of price feeds.
It then pushes a price update to an on-chain Pyth contract if any of the following conditions are met:

- Time difference: The on-chain price is older than `time_difference` seconds
  from the latest Pyth price.
- Price deviation: The latest Pyth price feed has changed more than `price_deviation` percent
  from the on-chain price feed price.
- Confidence ratio: The latest Pyth price feed has confidence to price ratio of more than
  `confidence_ratio`.

The parameters above are configured per price feed in a price configuration YAML file. The structure looks like this:

```yaml
- alias: A/USD # Arbitrary alias for the price feed. It is used in enhance logging.
  id: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef # id of a price feed, a 32-byte hex string.
  time_difference: 60 # Time difference threshold (in seconds) to push a newer price feed.
  price_deviation: 0.5 # The price deviation (%) threshold to push a newer price feed.
  confidence_ratio: 1 # The confidence/price (%) threshold to push a newer price feed.
  # Optional block to configure whether this feed can be early updated. If at least one feed meets the
  # triggering conditions above, all other feeds who meet the early update conditions will be included in
  # the submitted batch of prices. This logic takes advantage of the fact that adding a feed to a larger
  # batch of updates incurs a minimal gas cost. All fields below are optional (and interpreted as infinity if omitted)
  # and have the same semantics as the corresponding fields above.
  early_update:
    time_difference: 30
    price_deviation: 0.1
    confidence_ratio: 0.5
- ...
```

Two sample YAML configuration files are available in the root of this repo.

You can get the list of available price feeds from
[here](https://pyth.network/developers/price-feed-ids/).

To run the price pusher, please run the following commands, replacing the command line arguments as necessary:

```sh
# Please run the two following commands once from the root of the repo to build the code.
npm install
npx lerna run build --scope @pythnetwork/price-pusher --include-dependencies

# Navigate to the price_pusher folder
cd price_pusher

# For EVM
npm run start -- evm --endpoint wss://example-rpc.com \
    --pyth-contract-address 0xff1a0f4744e8582DF...... \
    --price-service-endpoint https://example-pyth-price.com \
    --price-config-file "path/to/price-config.testnet.sample.yaml" \
    --mnemonic-file "path/to/mnemonic.txt" \
    [--pushing-frequency 10] \
    [--polling-frequency 5] \
    [--override-gas-price-multiplier 1.1]

# For Injective
npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
    --pyth-contract-address inj1z60tg0... --price-service-endpoint "https://example-pyth-price.com" \
    --price-config-file "path/to/price-config.testnet.sample.yaml" \
    --mnemonic-file "path/to/mnemonic.txt" \
    [--pushing-frequency 10] \
    [--polling-frequency 5] \

# For Aptos
npm run start -- aptos --endpoint https://fullnode.testnet.aptoslabs.com/v1 \
    --pyth-contract-address 0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387 --price-service-endpoint "https://xc-testnet.pyth.network" \
    --price-config-file "./price-config.testnet.sample.yaml" \
    --mnemonic-file "path/to/mnemonic.txt" \
    [--pushing-frequency 10] \
    [--polling-frequency 5] \

# For Sui
npm run start -- sui \
  --endpoint https://sui-testnet-rpc.allthatnode.com \
  --pyth-package-id 0x975e063f398f720af4f33ec06a927f14ea76ca24f7f8dd544aa62ab9d5d15f44 \
  --pyth-state-id 0xd8afde3a48b4ff7212bd6829a150f43f59043221200d63504d981f62bff2e27a \
  --wormhole-package-id 0xcc029e2810f17f9f43f52262f40026a71fbdca40ed3803ad2884994361910b7e \
  --wormhole-state-id 0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02 \
  --price-feed-to-price-info-object-table-id 0xf8929174008c662266a1adde78e1e8e33016eb7ad37d379481e860b911e40ed5 \
  --price-service-endpoint https://xc-testnet.pyth.network \
  --mnemonic-file ./mnemonic \
  --price-config-file ./price-config.testnet.sample.yaml \
  [--pushing-frequency 10] \
  [--polling-frequency 5] \
  [--num-gas-objects 30]


# Or, run the price pusher docker image instead of building from the source
docker run public.ecr.aws/pyth-network/xc-price-pusher:v<version> -- <above-arguments>
```

### Command Line Arguments

To know more about the arguments the price-pusher accepts. You can run:

```sh
npm run start -- --help

# for specific network run
npm run start -- {network} --help
```

### Example

For example, to push `BTC/USD` and `BNB/USD` prices on Fantom testnet, run the following command:

```sh
npm run dev -- evm --endpoint https://endpoints.omniatech.io/v1/fantom/testnet/public \
    --pyth-contract-address 0xff1a0f4744e8582DF1aE09D5611b887B6a12925C --price-service-endpoint https://xc-testnet.pyth.network \
    --mnemonic-file "./mnemonic" --price-config-file "./price-config.testnet.sample.yaml"
```

[`price-config.testnet.sample.yaml`](./price-config.testnet.sample.yaml) contains configuration for `BTC/USD`
and `BNB/USD` price feeds on Pyth testnet. [`price-config.mainnet.sample.yaml`](./price-config.mainnet.sample.yaml)
contains the same configuration for `BTC/USD` and `BNB/USD` on Pyth mainnet.

You can also provide a config file instead of providing command line options, run the following command:

```sh
npm run start -- injective --config "./config.injective.sample.json"
```

[`config.injective.sample.json`](./config.injective.sample.json) contains configuration to publish on Injective testnet.

## Running using a standalone price service (via docker-compose)

Price pusher communicates with [Pyth price service][] to get the most recent price updates. Pyth price service listens to the
Wormhole network to get latest price updates, and serves REST and websocket APIs for consumers to fetch the updates.
Pyth hosts public endpoints for the price service; however, it is recommended to run it standalone to achieve more resiliency and
scalability.

This directory contains sample docker compose files ([testnet](./docker-compose.testnet.sample.yaml),
[mainnet](./docker-compose.mainnet.sample.yaml)) a price pusher and its dependencies, including a
price service and a Wormhole spy. A price service depends on a Wormhole spy. A spy listens to the Wormhole
network and reports all Pyth-related Wormhole messages to the price service.

To run the services via docker-compose, please modify the your target network (testnet, mainnet) sample docker-compose file to adjust the configurations.

Then, start the docker-compose like this:

```
docker-compose -f docker-compose.testnet.sample.yaml up
```

It will take a few minutes until all the services are up and running.

[pyth price service]: https://github.com/pyth-network/pyth-crosschain/tree/main/price_service/server

## Reliability

You can run multiple instances of the price pusher to increase the reliability. It is better to use
difference RPCs to get better reliability in case an RPC goes down. **If you use the same payer account
in different pushers, then due to blockchains nonce or sequence for accounts, a transaction won't be
pushed twiced and you won't pay additional costs most of the time.** However, there might be some race
condiitons in the RPCs because they are often behind a load balancer than can sometimes cause rejected
transactions land on-chain. You can reduce the chances of additional cost overhead by reducing the
pushing frequency.
