[price-pusher] support for aptos (#815)
* aptos price listener * price pusher aptos * add comment * update package lock * remove eslint disable comments * bump version * npm i at root * update readme * address feedback * update readme * json fix
This commit is contained in:
parent
1dbc592836
commit
3fc996d6f9
|
@ -11514,6 +11514,10 @@
|
|||
"resolved": "target_chains/cosmwasm/tools",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@pythnetwork/eth-oracle-swap-example-frontend": {
|
||||
"resolved": "target_chains/ethereum/examples/oracle_swap/app",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@pythnetwork/price-pusher": {
|
||||
"resolved": "price_pusher",
|
||||
"link": true
|
||||
|
@ -21067,9 +21071,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/aptos": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/aptos/-/aptos-1.6.0.tgz",
|
||||
"integrity": "sha512-5khjDwrDeNMDBFRcZAmETW20D+V2AqTdsgqkh6bvvl70BtRXdkitN0saM05gf1rK3atnO9PyUKO8iRaBDG5qtA==",
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/aptos/-/aptos-1.8.5.tgz",
|
||||
"integrity": "sha512-iQxliWesNHjGQ5YYXCyss9eg4+bDGQWqAZa73vprqGQ9tungK0cRjUI2fmnp63Ed6UG6rurHrL+b0ckbZAOZZQ==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.1.3",
|
||||
"@scure/bip39": "1.1.0",
|
||||
|
@ -50989,10 +50993,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/@pythnetwork/eth-oracle-swap-example-frontend": {
|
||||
"resolved": "target_chains/ethereum/examples/oracle_swap/app",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/traverse-chain": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
|
||||
|
@ -55110,13 +55110,14 @@
|
|||
},
|
||||
"price_pusher": {
|
||||
"name": "@pythnetwork/price-pusher",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@injectivelabs/sdk-ts": "1.10.72",
|
||||
"@pythnetwork/price-service-client": "*",
|
||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
||||
"@truffle/hdwallet-provider": "^2.1.3",
|
||||
"aptos": "^1.8.5",
|
||||
"joi": "^17.6.0",
|
||||
"web3": "^1.8.1",
|
||||
"web3-eth-contract": "^1.8.1",
|
||||
|
@ -57475,6 +57476,7 @@
|
|||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/oracle_swap/app": {
|
||||
"name": "@pythnetwork/eth-oracle-swap-example-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@pythnetwork/pyth-evm-js": "*",
|
||||
|
@ -66433,6 +66435,107 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@pythnetwork/eth-oracle-swap-example-frontend": {
|
||||
"version": "file:target_chains/ethereum/examples/oracle_swap/app",
|
||||
"requires": {
|
||||
"@pythnetwork/pyth-evm-js": "*",
|
||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.11.64",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"buffer": "^6.0.3",
|
||||
"ethers": "^5.7.2",
|
||||
"metamask-react": "^2.4.0",
|
||||
"prettier": "^2.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.8.4",
|
||||
"web-vitals": "^2.1.4",
|
||||
"web3": "^1.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/jest": {
|
||||
"version": "27.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz",
|
||||
"integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==",
|
||||
"requires": {
|
||||
"jest-matcher-utils": "^27.0.0",
|
||||
"pretty-format": "^27.0.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.18.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.28.tgz",
|
||||
"integrity": "sha512-SNMfiPqsiPoYfmyi+2qnDO4nZyMIOCab/CW+Slcml0lhIzkOizYzWtt/A7tgB3TSitd+YJKi8fSC2Cpm/VCp7A=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
|
||||
},
|
||||
"buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz",
|
||||
"integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ=="
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
|
||||
"integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^27.5.1",
|
||||
"jest-get-type": "^27.5.1",
|
||||
"pretty-format": "^27.5.1"
|
||||
}
|
||||
},
|
||||
"jest-get-type": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz",
|
||||
"integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw=="
|
||||
},
|
||||
"jest-matcher-utils": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz",
|
||||
"integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"jest-diff": "^27.5.1",
|
||||
"jest-get-type": "^27.5.1",
|
||||
"pretty-format": "^27.5.1"
|
||||
}
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pythnetwork/price-pusher": {
|
||||
"version": "file:price_pusher",
|
||||
"requires": {
|
||||
|
@ -66445,6 +66548,7 @@
|
|||
"@types/yargs": "^17.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
||||
"@typescript-eslint/parser": "^5.20.0",
|
||||
"aptos": "^1.8.5",
|
||||
"eslint": "^8.13.0",
|
||||
"jest": "^27.5.1",
|
||||
"joi": "^17.6.0",
|
||||
|
@ -77292,9 +77396,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"aptos": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/aptos/-/aptos-1.6.0.tgz",
|
||||
"integrity": "sha512-5khjDwrDeNMDBFRcZAmETW20D+V2AqTdsgqkh6bvvl70BtRXdkitN0saM05gf1rK3atnO9PyUKO8iRaBDG5qtA==",
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/aptos/-/aptos-1.8.5.tgz",
|
||||
"integrity": "sha512-iQxliWesNHjGQ5YYXCyss9eg4+bDGQWqAZa73vprqGQ9tungK0cRjUI2fmnp63Ed6UG6rurHrL+b0ckbZAOZZQ==",
|
||||
"requires": {
|
||||
"@noble/hashes": "1.1.3",
|
||||
"@scure/bip39": "1.1.0",
|
||||
|
@ -101582,107 +101686,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"@pythnetwork/eth-oracle-swap-example-frontend": {
|
||||
"version": "file:target_chains/ethereum/examples/oracle_swap/app",
|
||||
"requires": {
|
||||
"@pythnetwork/pyth-evm-js": "*",
|
||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.11.64",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"buffer": "^6.0.3",
|
||||
"ethers": "^5.7.2",
|
||||
"metamask-react": "^2.4.0",
|
||||
"prettier": "^2.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.8.4",
|
||||
"web-vitals": "^2.1.4",
|
||||
"web3": "^1.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/jest": {
|
||||
"version": "27.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz",
|
||||
"integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==",
|
||||
"requires": {
|
||||
"jest-matcher-utils": "^27.0.0",
|
||||
"pretty-format": "^27.0.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.18.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.28.tgz",
|
||||
"integrity": "sha512-SNMfiPqsiPoYfmyi+2qnDO4nZyMIOCab/CW+Slcml0lhIzkOizYzWtt/A7tgB3TSitd+YJKi8fSC2Cpm/VCp7A=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
|
||||
},
|
||||
"buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz",
|
||||
"integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ=="
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
|
||||
"integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^27.5.1",
|
||||
"jest-get-type": "^27.5.1",
|
||||
"pretty-format": "^27.5.1"
|
||||
}
|
||||
},
|
||||
"jest-get-type": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz",
|
||||
"integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw=="
|
||||
},
|
||||
"jest-matcher-utils": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz",
|
||||
"integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"jest-diff": "^27.5.1",
|
||||
"jest-get-type": "^27.5.1",
|
||||
"pretty-format": "^27.5.1"
|
||||
}
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"traverse-chain": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
|
||||
|
|
|
@ -71,6 +71,14 @@ npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
|
|||
[--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] \
|
||||
|
||||
|
||||
# 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>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"endpoint": "https://fullnode.testnet.aptoslabs.com/v1",
|
||||
"pyth-contract-address": "0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387",
|
||||
"price-service-endpoint": "https://xc-testnet.pyth.network",
|
||||
"mnemonic-file": "./mnemonic",
|
||||
"price-config-file": "./price-config.testnet.sample.yaml"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/price-pusher",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.0",
|
||||
"description": "Pyth Price Pusher",
|
||||
"homepage": "https://pyth.network",
|
||||
"main": "lib/index.js",
|
||||
|
@ -55,6 +55,7 @@
|
|||
"@pythnetwork/price-service-client": "*",
|
||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
||||
"@truffle/hdwallet-provider": "^2.1.3",
|
||||
"aptos": "^1.8.5",
|
||||
"joi": "^17.6.0",
|
||||
"web3": "^1.8.1",
|
||||
"web3-eth-contract": "^1.8.1",
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
import {
|
||||
ChainPriceListener,
|
||||
IPricePusher,
|
||||
PriceInfo,
|
||||
PriceItem,
|
||||
} from "../interface";
|
||||
import { AptosAccount, AptosClient, TxnBuilderTypes } from "aptos";
|
||||
import { DurationInSeconds } from "../utils";
|
||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
||||
import { PushAttempt } from "../common";
|
||||
|
||||
export class AptosPriceListener extends ChainPriceListener {
|
||||
constructor(
|
||||
private pythModule: string,
|
||||
private endpoint: string,
|
||||
priceItems: PriceItem[],
|
||||
config: {
|
||||
pollingFrequency: DurationInSeconds;
|
||||
}
|
||||
) {
|
||||
super("aptos", config.pollingFrequency, priceItems);
|
||||
}
|
||||
|
||||
async getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined> {
|
||||
try {
|
||||
const client = new AptosClient(this.endpoint);
|
||||
|
||||
const res = await client.getAccountResource(
|
||||
this.pythModule,
|
||||
`${this.pythModule}::state::LatestPriceInfo`
|
||||
);
|
||||
|
||||
// This depends upon the pyth contract storage on Aptos and should not be undefined.
|
||||
// If undefined, there has been some change and we would need to update accordingly.
|
||||
const handle = (res.data as any).info.handle;
|
||||
|
||||
const priceItemRes = await client.getTableItem(handle, {
|
||||
key_type: `${this.pythModule}::price_identifier::PriceIdentifier`,
|
||||
value_type: `${this.pythModule}::price_info::PriceInfo`,
|
||||
key: {
|
||||
bytes: priceId,
|
||||
},
|
||||
});
|
||||
|
||||
const multiplier =
|
||||
priceItemRes.price_feed.price.price.negative === true ? -1 : 1;
|
||||
const price =
|
||||
multiplier * Number(priceItemRes.price_feed.price.price.magnitude);
|
||||
|
||||
console.log(
|
||||
`Polled an Aptos on-chain price for feed ${this.priceIdToAlias.get(
|
||||
priceId
|
||||
)} (${priceId}).`
|
||||
);
|
||||
|
||||
return {
|
||||
price: price.toString(),
|
||||
conf: priceItemRes.price_feed.price.conf,
|
||||
publishTime: Number(priceItemRes.price_feed.price.timestamp),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Polling Aptos on-chain price for ${priceId} failed. Error:`
|
||||
);
|
||||
console.error(e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AptosPricePusher implements IPricePusher {
|
||||
private lastPushAttempt: PushAttempt | undefined;
|
||||
|
||||
private readonly accountHDPath = "m/44'/637'/0'/0'/0'";
|
||||
constructor(
|
||||
private priceServiceConnection: PriceServiceConnection,
|
||||
private pythContractAddress: string,
|
||||
private endpoint: string,
|
||||
private mnemonic: string,
|
||||
private overrideGasPriceMultiplier: number
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Gets price update data which then can be submitted to the Pyth contract to update the prices.
|
||||
* This will throw an axios error if there is a network problem or the price service returns a non-ok response (e.g: Invalid price ids)
|
||||
*
|
||||
* @param priceIds Array of hex-encoded price ids.
|
||||
* @returns Array of price update data.
|
||||
*/
|
||||
async getPriceFeedsUpdateData(priceIds: string[]): Promise<number[][]> {
|
||||
// Fetch the latest price feed update VAAs from the price service
|
||||
const latestVaas = await this.priceServiceConnection.getLatestVaas(
|
||||
priceIds
|
||||
);
|
||||
return latestVaas.map((vaa) => Array.from(Buffer.from(vaa, "base64")));
|
||||
}
|
||||
|
||||
async updatePriceFeed(
|
||||
priceIds: string[],
|
||||
pubTimesToPush: number[]
|
||||
): Promise<void> {
|
||||
if (priceIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (priceIds.length !== pubTimesToPush.length)
|
||||
throw new Error("Invalid arguments");
|
||||
|
||||
let priceFeedUpdateData;
|
||||
try {
|
||||
// get the latest VAAs for updatePriceFeed and then push them
|
||||
priceFeedUpdateData = await this.getPriceFeedsUpdateData(priceIds);
|
||||
} catch (e) {
|
||||
console.error("Error fetching the latest vaas to push");
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const account = AptosAccount.fromDerivePath(
|
||||
this.accountHDPath,
|
||||
this.mnemonic
|
||||
);
|
||||
const client = new AptosClient(this.endpoint);
|
||||
|
||||
const rawTx = await client.generateTransaction(account.address(), {
|
||||
function: `${this.pythContractAddress}::pyth::update_price_feeds_if_fresh_with_funder`,
|
||||
type_arguments: [],
|
||||
arguments: [
|
||||
priceFeedUpdateData,
|
||||
priceIds.map((priceId) => Buffer.from(priceId, "hex")),
|
||||
pubTimesToPush,
|
||||
],
|
||||
});
|
||||
|
||||
const simulation = await client.simulateTransaction(account, rawTx, {
|
||||
estimateGasUnitPrice: true,
|
||||
estimateMaxGasAmount: true,
|
||||
estimatePrioritizedGasUnitPrice: true,
|
||||
});
|
||||
|
||||
// Transactions on Aptos can be prioritized by paying a higher gas unit price.
|
||||
// We are storing the gas unit price paid for the last transaction.
|
||||
// If that transaction is not added to the block, we are increasing the gas unit price
|
||||
// by multiplying the old gas unit price with `this.overrideGasPriceMultiplier`.
|
||||
// After which we are sending a transaction with the same sequence number as the last
|
||||
// transaction. Since they have the same sequence number only one of them will be added to
|
||||
// the block and we won't be paying fees twice.
|
||||
let gasUnitPrice = Number(simulation[0].gas_unit_price);
|
||||
if (
|
||||
this.lastPushAttempt !== undefined &&
|
||||
Number(simulation[0].sequence_number) === this.lastPushAttempt.nonce
|
||||
) {
|
||||
const newGasUnitPrice = Number(
|
||||
this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier
|
||||
);
|
||||
if (gasUnitPrice < newGasUnitPrice) gasUnitPrice = newGasUnitPrice;
|
||||
}
|
||||
|
||||
const gasUsed = Number(simulation[0].gas_used) * 1.5;
|
||||
const maxGasAmount = Number(gasUnitPrice * gasUsed);
|
||||
|
||||
const rawTxWithFee = new TxnBuilderTypes.RawTransaction(
|
||||
rawTx.sender,
|
||||
rawTx.sequence_number,
|
||||
rawTx.payload,
|
||||
BigInt(maxGasAmount.toFixed()),
|
||||
BigInt(gasUnitPrice.toFixed()),
|
||||
rawTx.expiration_timestamp_secs,
|
||||
rawTx.chain_id
|
||||
);
|
||||
|
||||
const signedTx = await client.signTransaction(account, rawTxWithFee);
|
||||
const pendingTx = await client.submitTransaction(signedTx);
|
||||
|
||||
console.log("Succesfully broadcasted txHash:", pendingTx.hash);
|
||||
|
||||
// Update lastAttempt
|
||||
this.lastPushAttempt = {
|
||||
nonce: Number(pendingTx.sequence_number),
|
||||
gasPrice: gasUnitPrice,
|
||||
};
|
||||
return;
|
||||
} catch (e: any) {
|
||||
console.error("Error executing messages");
|
||||
console.log(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
||||
import * as options from "../options";
|
||||
import { readPriceConfigFile } from "../price-config";
|
||||
import fs from "fs";
|
||||
import { PythPriceListener } from "../pyth-price-listener";
|
||||
import { Controller } from "../controller";
|
||||
import { Options } from "yargs";
|
||||
import { AptosPriceListener, AptosPricePusher } from "./aptos";
|
||||
|
||||
export default {
|
||||
command: "aptos",
|
||||
describe: "run price pusher for aptos",
|
||||
builder: {
|
||||
endpoint: {
|
||||
description:
|
||||
"RPC endpoint endpoint URL for aptos. The pusher will periodically" +
|
||||
"poll for updates. The polling interval is configurable via the " +
|
||||
"`polling-frequency` command-line argument.",
|
||||
type: "string",
|
||||
required: true,
|
||||
} as Options,
|
||||
"override-gas-price-multiplier": {
|
||||
description:
|
||||
"Multiply the gas price by this number if the transaction is not landing to override it. Default 2",
|
||||
type: "number",
|
||||
required: false,
|
||||
default: 2,
|
||||
} as Options,
|
||||
...options.priceConfigFile,
|
||||
...options.priceServiceEndpoint,
|
||||
...options.mnemonicFile,
|
||||
...options.pythContractAddress,
|
||||
...options.pollingFrequency,
|
||||
...options.pushingFrequency,
|
||||
},
|
||||
handler: function (argv: any) {
|
||||
// FIXME: type checks for this
|
||||
const {
|
||||
endpoint,
|
||||
priceConfigFile,
|
||||
priceServiceEndpoint,
|
||||
mnemonicFile,
|
||||
pythContractAddress,
|
||||
pushingFrequency,
|
||||
pollingFrequency,
|
||||
overrideGasPriceMultiplier,
|
||||
} = argv;
|
||||
|
||||
const priceConfigs = readPriceConfigFile(priceConfigFile);
|
||||
const priceServiceConnection = new PriceServiceConnection(
|
||||
priceServiceEndpoint,
|
||||
{
|
||||
logger: {
|
||||
// Log only warnings and errors from the price service client
|
||||
info: () => undefined,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: () => undefined,
|
||||
trace: () => undefined,
|
||||
},
|
||||
}
|
||||
);
|
||||
const mnemonic = fs.readFileSync(mnemonicFile, "utf-8").trim();
|
||||
|
||||
const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
|
||||
|
||||
const pythListener = new PythPriceListener(
|
||||
priceServiceConnection,
|
||||
priceItems
|
||||
);
|
||||
|
||||
const aptosListener = new AptosPriceListener(
|
||||
pythContractAddress,
|
||||
endpoint,
|
||||
priceItems,
|
||||
{ pollingFrequency }
|
||||
);
|
||||
const aptosPusher = new AptosPricePusher(
|
||||
priceServiceConnection,
|
||||
pythContractAddress,
|
||||
endpoint,
|
||||
mnemonic,
|
||||
overrideGasPriceMultiplier
|
||||
);
|
||||
|
||||
const controller = new Controller(
|
||||
priceConfigs,
|
||||
pythListener,
|
||||
aptosListener,
|
||||
aptosPusher,
|
||||
{ pushingFrequency }
|
||||
);
|
||||
|
||||
controller.start();
|
||||
},
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
export type PushAttempt = {
|
||||
nonce: number;
|
||||
gasPrice: number;
|
||||
};
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from "@pythnetwork/price-service-client";
|
||||
import { CustomGasStation } from "./custom-gas-station";
|
||||
import { Provider } from "web3/providers";
|
||||
import { PushAttempt } from "../common";
|
||||
|
||||
export class EvmPriceListener extends ChainPriceListener {
|
||||
private pythContractFactory: PythContractFactory;
|
||||
|
@ -117,11 +118,6 @@ export class EvmPriceListener extends ChainPriceListener {
|
|||
}
|
||||
}
|
||||
|
||||
type PushAttempt = {
|
||||
nonce: number;
|
||||
gasPrice: number;
|
||||
};
|
||||
|
||||
export class EvmPricePusher implements IPricePusher {
|
||||
private customGasStation?: CustomGasStation;
|
||||
private pythContract: Contract;
|
||||
|
|
|
@ -3,10 +3,12 @@ import yargs from "yargs";
|
|||
import { hideBin } from "yargs/helpers";
|
||||
import injective from "./injective/command";
|
||||
import evm from "./evm/command";
|
||||
import aptos from "./aptos/command";
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.config("config")
|
||||
.global("config")
|
||||
.command(evm)
|
||||
.command(injective)
|
||||
.command(aptos)
|
||||
.help().argv;
|
||||
|
|
Loading…
Reference in New Issue