Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement simple price delivery services #5

Merged
merged 3 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ When verified the pyth contract verifies the merkle proof and extracts the price

### Oracle contract
Proof logic taken from:

[Wormhole proof logic Cosmwasm](https://github.com/wormhole-foundation/wormhole/tree/main/cosmwasm/contracts/wormhole/src)

[Pyth merkle proof logic Cosmwasm](https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/cosmwasm/contracts/pyth/src)

[Pyth merkle proof logic Solidity](https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/ethereum/contracts/contracts/pyth)


Expand Down
61 changes: 61 additions & 0 deletions price-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Price monitor

## Start polling service
``` bash
npm run start-poll
```

## Start streaming service
``` bash
npm run start-stream
```
## Configuring service

- **pyth_url**: The URL for the Pyth network hermes.
- Example: `https://hermes.pyth.network`

- **icon_url**: The URL for the ICON network API.
- Example: `https://lisbon.net.solidwallet.io/api/v3`

- **icon_pk**: Wallet private key for the ICON network.
- Example: `""`

- **address**: The blockchain address for the contract on the ICON network,
- Example: `cx7380205103a9076aae26d1c761a8bb6652ecf30f`

- **nid**: Network ID for the ICON network, specifying the network environment (e.g., mainnet, testnet).
- Example: `0x2`

- **priceIds**: A list of identifiers for specific data feeds provided by the Pyth network. [Feeds](https://pyth.network/price-feeds)
- Example:
``` json
[
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
"0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"
"0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"
"0xb7a8eba68a997cd0210c2e1e4ee811ad2d174b3611c22d9ebf16f4cb7e9ba850"
]
```
- **priceChangeThreshold**: The value price change threshold used in streaming, in %, determining when prices should be updated.
- Example: `0.2`

- **interval**: The time interval, in seconds, between price updates. In streaming is only used if priceChangeThreshold is not hit within the interval
- Example: `300`

# How to setup a price delivery service using PM2
To for example setup a price monitor on a EC2 instance:

## Setup Polling service
``` bash
pm2 start ./node_modules/.bin/ts-node --name "price-monitor" -- src/servicePoll.ts
```
## Setup Streaming service
``` bash
pm2 start ./node_modules/.bin/ts-node --name "price-monitor" -- src/serviceStreaming.ts
```

Configure service to start on startup
``` bash
pm2 save
pm2 startup
```
2 changes: 2 additions & 0 deletions price-service/example_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"pyth_url": "https://hermes.pyth.network",
"icon_url": "https://lisbon.net.solidwallet.io/api/v3",
"icon_pk": "",
"address": "cx7380205103a9076aae26d1c761a8bb6652ecf30f",
"nid": "0x2",
"priceIds": [
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43",
"0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace",
Expand Down
3 changes: 2 additions & 1 deletion price-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "ts-node src/index.ts"
"start-stream": "ts-node src/serviceStream.ts",
"start-poll": "ts-node src/servicePoll.ts"
},
"keywords": [],
"author": "",
Expand Down
45 changes: 20 additions & 25 deletions price-service/src/priceMonitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,66 @@ export class PriceMonitor {
private provider:HttpProvider;
private iconService: IconService;
private wallet: Wallet;
private address: string;
private nid: string;

constructor(config: any) {
AntonAndell marked this conversation as resolved.
Show resolved Hide resolved
this.threshold = config.threshold / 100;
this.threshold = config.priceChangeThreshold / 100;
this.minInterval = config.interval;
this.provider = new HttpProvider(config.icon_url);
this.iconService = new IconService(this.provider);
this.wallet = Wallet.loadPrivateKey(config.icon_pk);
this.address = config.address;
this.nid = config.nid;
}

public async onPriceUpdate(feed: PriceFeed): Promise<void> {

if (this.processing) {
console.log(`Skipping update`);
return;
}

this.processing = true

try {
const parsed = feed.parsed;

for (let priceEntry of parsed) {
for (let priceEntry of feed.parsed) {
let lastUpdate = this.prices.get(priceEntry.id);
if (lastUpdate) {

if (lastUpdate) {
const priceChange = Math.abs(priceEntry.price.price - lastUpdate.price) / lastUpdate.price;
console.log(priceChange);

if (priceChange >= this.threshold || priceEntry.price.publish_time - lastUpdate.publish_time >= this.minInterval) {
await this.updatePrice(feed);
return;
const timeSinceLastUpdate = priceEntry.price.publish_time - lastUpdate.publish_time
if (priceChange < this.threshold && timeSinceLastUpdate < this.minInterval) {
continue;
}
} else {
await this.updatePrice(feed);
return;
}

await this.updatePrice(feed);
feed.parsed.forEach(priceEntry => {
this.prices.set(priceEntry.id, priceEntry.price)
})
return;
CyrusVorwald marked this conversation as resolved.
Show resolved Hide resolved
};
} finally {
this.processing = false;
}
}

private async updatePrice(feed: PriceFeed): Promise<void> {
public async updatePrice(feed: PriceFeed): Promise<void> {
AntonAndell marked this conversation as resolved.
Show resolved Hide resolved
const timestamp = (new Date()).getTime() * 1000;
let tx = new CallTransactionBuilder()
.nid("0x2")
.nid(this.nid)
.from(this.wallet.getAddress())
.stepLimit(400000000)
AntonAndell marked this conversation as resolved.
Show resolved Hide resolved
.timestamp(timestamp)
.to("cx7380205103a9076aae26d1c761a8bb6652ecf30f")
.to(this.address)
.method("updatePriceFeed")
.params({
"data": feed.binary.data,
})
.version("0x3")
.build();

const signedTransaction: SignedTransaction = new SignedTransaction(tx, this.wallet);
const res = await this.iconService.sendTransaction(signedTransaction).execute();
console.log(res);
const parsed = feed.parsed;
parsed.forEach(priceEntry => {
this.prices.set(priceEntry.id, priceEntry.price)
})
const signedTransaction: SignedTransaction = new SignedTransaction(tx, this.wallet);
const res = await this.iconService.sendTransaction(signedTransaction).execute();
console.log(res)
}
}
15 changes: 15 additions & 0 deletions price-service/src/servicePoll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { PriceMonitor } from "./priceMonitor";

const { HermesClient } = require('@pythnetwork/hermes-client');
const fs = require('fs');

const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
const priceMonitor = new PriceMonitor(config);
const connection = new HermesClient(config.pyth_url);

async function poll() {
const priceUpdates = await connection.getLatestPriceUpdates(config.priceIds);
AntonAndell marked this conversation as resolved.
Show resolved Hide resolved
priceMonitor.updatePrice(priceUpdates)
}

setInterval(poll, config.interval*1000)
20 changes: 20 additions & 0 deletions price-service/src/serviceStream.ts
AntonAndell marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { PriceMonitor } from "./priceMonitor";

const { HermesClient } = require('@pythnetwork/hermes-client');
const fs = require('fs');

const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
const priceMonitor = new PriceMonitor(config);
(async () => {
try {
const connection = new HermesClient(config.pyth_url);
const eventSource = await connection.getPriceUpdatesStream(config.priceIds);
eventSource.onmessage = (event: MessageEvent) => {
const data = JSON.parse(event.data);
priceMonitor.onPriceUpdate(data);
};

} catch (error) {
process.exit(1);
}
})();