[Sui 21/x] - pyth-sui-js SDK (#1004)
* Add sui js sdk * Update dependencies and package lock --------- Co-authored-by: Amin Moghaddam <amin@pyth.network>
This commit is contained in:
parent
f36bd21f31
commit
55129e5b89
|
@ -20,6 +20,7 @@
|
||||||
"target_chains/ethereum/sdk/js",
|
"target_chains/ethereum/sdk/js",
|
||||||
"target_chains/ethereum/sdk/solidity",
|
"target_chains/ethereum/sdk/solidity",
|
||||||
"target_chains/ethereum/examples/oracle_swap/app",
|
"target_chains/ethereum/examples/oracle_swap/app",
|
||||||
|
"target_chains/sui/sdk/js",
|
||||||
"third_party/pyth/p2w-relay",
|
"third_party/pyth/p2w-relay",
|
||||||
"wormhole_attester/sdk/js",
|
"wormhole_attester/sdk/js",
|
||||||
"contract_manager"
|
"contract_manager"
|
||||||
|
@ -12005,6 +12006,10 @@
|
||||||
"resolved": "target_chains/ethereum/sdk/solidity",
|
"resolved": "target_chains/ethereum/sdk/solidity",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@pythnetwork/pyth-sui-js": {
|
||||||
|
"resolved": "target_chains/sui/sdk/js",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@pythnetwork/pyth-terra-js": {
|
"node_modules/@pythnetwork/pyth-terra-js": {
|
||||||
"resolved": "target_chains/cosmwasm/sdk/js",
|
"resolved": "target_chains/cosmwasm/sdk/js",
|
||||||
"link": true
|
"link": true
|
||||||
|
@ -59657,6 +59662,221 @@
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"target_chains/sui/sdk/js": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@mysten/sui.js": "^0.32.2",
|
||||||
|
"@pythnetwork/price-service-client": "*",
|
||||||
|
"buffer": "^6.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@truffle/hdwallet-provider": "^2.1.5",
|
||||||
|
"@types/ethereum-protocol": "^1.0.2",
|
||||||
|
"@types/jest": "^29.4.0",
|
||||||
|
"@types/node": "^18.11.18",
|
||||||
|
"@types/web3-provider-engine": "^14.0.1",
|
||||||
|
"@types/yargs": "^17.0.20",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||||
|
"@typescript-eslint/parser": "^5.21.0",
|
||||||
|
"eslint": "^8.14.0",
|
||||||
|
"jest": "^29.4.1",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"ts-jest": "^29.0.5",
|
||||||
|
"typescript": "^4.6.3",
|
||||||
|
"web3": "^1.8.2",
|
||||||
|
"yargs": "^17.0.20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/@mysten/sui.js": {
|
||||||
|
"version": "0.32.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.32.2.tgz",
|
||||||
|
"integrity": "sha512-/Hm4xkGolJhqj8FvQr7QSHDTlxIvL52mtbOao9f75YjrBh7y1Uh9kbJSY7xiTF1NY9sv6p5hUVlYRJuM0Hvn9A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@mysten/bcs": "0.7.1",
|
||||||
|
"@noble/curves": "^1.0.0",
|
||||||
|
"@noble/hashes": "^1.3.0",
|
||||||
|
"@scure/bip32": "^1.3.0",
|
||||||
|
"@scure/bip39": "^1.2.0",
|
||||||
|
"@suchipi/femver": "^1.0.0",
|
||||||
|
"jayson": "^4.0.0",
|
||||||
|
"rpc-websockets": "^7.5.1",
|
||||||
|
"superstruct": "^1.0.3",
|
||||||
|
"tweetnacl": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/@noble/curves": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "1.3.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/@scure/bip32": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/curves": "~1.1.0",
|
||||||
|
"@noble/hashes": "~1.3.1",
|
||||||
|
"@scure/base": "~1.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/@scure/bip39": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "~1.3.0",
|
||||||
|
"@scure/base": "~1.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/cliui": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/jayson": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/connect": "^3.4.33",
|
||||||
|
"@types/node": "^12.12.54",
|
||||||
|
"@types/ws": "^7.4.4",
|
||||||
|
"commander": "^2.20.3",
|
||||||
|
"delay": "^5.0.0",
|
||||||
|
"es6-promisify": "^5.0.0",
|
||||||
|
"eyes": "^0.1.8",
|
||||||
|
"isomorphic-ws": "^4.0.1",
|
||||||
|
"json-stringify-safe": "^5.0.1",
|
||||||
|
"JSONStream": "^1.3.5",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"ws": "^7.4.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"jayson": "bin/jayson.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/jayson/node_modules/@types/node": {
|
||||||
|
"version": "12.20.55",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
|
||||||
|
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/superstruct": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/ws": {
|
||||||
|
"version": "7.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||||
|
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/yargs": {
|
||||||
|
"version": "17.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^8.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target_chains/sui/sdk/js/node_modules/yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"third_party/pyth/p2w-relay": {
|
"third_party/pyth/p2w-relay": {
|
||||||
"name": "pyth_relay",
|
"name": "pyth_relay",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
@ -71429,6 +71649,158 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@pythnetwork/pyth-sui-js": {
|
||||||
|
"version": "file:target_chains/sui/sdk/js",
|
||||||
|
"requires": {
|
||||||
|
"@mysten/sui.js": "^0.32.2",
|
||||||
|
"@pythnetwork/price-service-client": "*",
|
||||||
|
"@truffle/hdwallet-provider": "^2.1.5",
|
||||||
|
"@types/ethereum-protocol": "^1.0.2",
|
||||||
|
"@types/jest": "^29.4.0",
|
||||||
|
"@types/node": "^18.11.18",
|
||||||
|
"@types/web3-provider-engine": "^14.0.1",
|
||||||
|
"@types/yargs": "^17.0.20",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||||
|
"@typescript-eslint/parser": "^5.21.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"eslint": "^8.14.0",
|
||||||
|
"jest": "^29.4.1",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"ts-jest": "^29.0.5",
|
||||||
|
"typescript": "^4.6.3",
|
||||||
|
"web3": "^1.8.2",
|
||||||
|
"yargs": "^17.0.20"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mysten/sui.js": {
|
||||||
|
"version": "0.32.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.32.2.tgz",
|
||||||
|
"integrity": "sha512-/Hm4xkGolJhqj8FvQr7QSHDTlxIvL52mtbOao9f75YjrBh7y1Uh9kbJSY7xiTF1NY9sv6p5hUVlYRJuM0Hvn9A==",
|
||||||
|
"requires": {
|
||||||
|
"@mysten/bcs": "0.7.1",
|
||||||
|
"@noble/curves": "^1.0.0",
|
||||||
|
"@noble/hashes": "^1.3.0",
|
||||||
|
"@scure/bip32": "^1.3.0",
|
||||||
|
"@scure/bip39": "^1.2.0",
|
||||||
|
"@suchipi/femver": "^1.0.0",
|
||||||
|
"jayson": "^4.0.0",
|
||||||
|
"rpc-websockets": "^7.5.1",
|
||||||
|
"superstruct": "^1.0.3",
|
||||||
|
"tweetnacl": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@noble/curves": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
|
||||||
|
"requires": {
|
||||||
|
"@noble/hashes": "1.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@noble/hashes": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="
|
||||||
|
},
|
||||||
|
"@scure/bip32": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
|
||||||
|
"requires": {
|
||||||
|
"@noble/curves": "~1.1.0",
|
||||||
|
"@noble/hashes": "~1.3.1",
|
||||||
|
"@scure/base": "~1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@scure/bip39": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
|
||||||
|
"requires": {
|
||||||
|
"@noble/hashes": "~1.3.0",
|
||||||
|
"@scure/base": "~1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cliui": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jayson": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
|
||||||
|
"requires": {
|
||||||
|
"@types/connect": "^3.4.33",
|
||||||
|
"@types/node": "^12.12.54",
|
||||||
|
"@types/ws": "^7.4.4",
|
||||||
|
"commander": "^2.20.3",
|
||||||
|
"delay": "^5.0.0",
|
||||||
|
"es6-promisify": "^5.0.0",
|
||||||
|
"eyes": "^0.1.8",
|
||||||
|
"isomorphic-ws": "^4.0.1",
|
||||||
|
"json-stringify-safe": "^5.0.1",
|
||||||
|
"JSONStream": "^1.3.5",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"ws": "^7.4.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": {
|
||||||
|
"version": "12.20.55",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
|
||||||
|
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"superstruct": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg=="
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "7.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||||
|
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"yargs": {
|
||||||
|
"version": "17.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"cliui": "^8.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@pythnetwork/pyth-terra-js": {
|
"@pythnetwork/pyth-terra-js": {
|
||||||
"version": "file:target_chains/cosmwasm/sdk/js",
|
"version": "file:target_chains/cosmwasm/sdk/js",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"target_chains/ethereum/sdk/js",
|
"target_chains/ethereum/sdk/js",
|
||||||
"target_chains/ethereum/sdk/solidity",
|
"target_chains/ethereum/sdk/solidity",
|
||||||
"target_chains/ethereum/examples/oracle_swap/app",
|
"target_chains/ethereum/examples/oracle_swap/app",
|
||||||
|
"target_chains/sui/sdk/js",
|
||||||
"third_party/pyth/p2w-relay",
|
"third_party/pyth/p2w-relay",
|
||||||
"wormhole_attester/sdk/js",
|
"wormhole_attester/sdk/js",
|
||||||
"contract_manager"
|
"contract_manager"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.3.4",
|
"@types/chai": "^4.3.4",
|
||||||
"@types/mocha": "^10.0.1",
|
"@types/mocha": "^10.0.1",
|
||||||
"@types/node": "^18.16.3"
|
"@types/node": "^18.17.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ async function main() {
|
||||||
];
|
];
|
||||||
const priceFeedVAAs = await connection.getLatestVaas(data);
|
const priceFeedVAAs = await connection.getLatestVaas(data);
|
||||||
//console.log("number of VAAs: ", priceFeedVAAs.length)
|
//console.log("number of VAAs: ", priceFeedVAAs.length)
|
||||||
console.log(priceFeedVAAs);
|
console.log(Buffer.from(priceFeedVAAs[0], "base64").toString("hex"));
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
|
@ -73,6 +73,8 @@ async function update_price_feed_using_accumulator_message(
|
||||||
console.log("PYTH_STATE: ", PYTH_STATE);
|
console.log("PYTH_STATE: ", PYTH_STATE);
|
||||||
console.log("WORM_PACKAGE: ", WORM_PACKAGE);
|
console.log("WORM_PACKAGE: ", WORM_PACKAGE);
|
||||||
console.log("WORM_STATE: ", WORM_STATE);
|
console.log("WORM_STATE: ", WORM_STATE);
|
||||||
|
console.log("accumulator_message: ", accumulator_message);
|
||||||
|
console.log("price_info_object_id: ", price_info_object_id);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"vaa parsed from accumulator: ",
|
"vaa parsed from accumulator: ",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["mocha", "chai"],
|
"types": ["mocha", "chai", "node"],
|
||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
"lib": ["es2020", "DOM"],
|
"lib": ["es2020", "DOM"],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
plugins: ["@typescript-eslint"],
|
||||||
|
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
lib
|
|
@ -0,0 +1,151 @@
|
||||||
|
# Pyth Sui JS SDK
|
||||||
|
|
||||||
|
[Pyth](https://pyth.network/) provides real-time pricing data in a variety of asset classes, including cryptocurrency, equities, FX and commodities. This library allows you to use these real-time prices on the [Sui network](https://sui.io/).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### npm
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm install --save @pythnetwork/pyth-sui-js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Yarn
|
||||||
|
|
||||||
|
```
|
||||||
|
$ yarn add @pythnetwork/pyth-sui-js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
Pyth stores prices off-chain to minimize gas fees, which allows us to offer a wider selection of products and faster update times.
|
||||||
|
See [On-Demand Updates](https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand) for more information about this approach.
|
||||||
|
Typically, to use Pyth prices on chain,
|
||||||
|
they must be fetched from an off-chain price service. The `SuiPriceServiceConnection` class can be used to interact with these services,
|
||||||
|
providing a way to fetch these prices directly in your code. The following example wraps an existing RPC provider and shows how to obtain
|
||||||
|
Pyth prices and submit them to the network:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const connection = new SuiPriceServiceConnection(
|
||||||
|
"https://hermes-beta.pyth.network"
|
||||||
|
); // See Price Service endpoints section below for other endpoints
|
||||||
|
|
||||||
|
const priceIds = [
|
||||||
|
// You can find the ids of prices at https://pyth.network/developers/price-feed-ids#sui-testnet
|
||||||
|
"0xf9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b", // BTC/USD price id in testnet
|
||||||
|
"0xca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6", // ETH/USD price id in testnet
|
||||||
|
];
|
||||||
|
|
||||||
|
// In order to use Pyth prices in your protocol you need to submit the price update data to Pyth contract in your target
|
||||||
|
// chain. `getPriceUpdateData` creates the update data which can be submitted to your contract.
|
||||||
|
|
||||||
|
const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIds);
|
||||||
|
```
|
||||||
|
|
||||||
|
## On-chain prices
|
||||||
|
|
||||||
|
### **_Important Note for Integrators_**
|
||||||
|
|
||||||
|
Your Sui Move module **should NOT** have a hard-coded call to `pyth::update_single_price_feed`. In other words, the Sui Pyth `pyth::update_single_price_feed` entry point should never be called by a contract, instead it should be called directly from client code (e.g. Typescript or Rust).
|
||||||
|
|
||||||
|
This is because when a Sui contract is [upgraded](https://docs.sui.io/build/package-upgrades), the new address is different from the original. If your module has a hard-coded call to `pyth::update_single_price_feed` living at a fixed call-site, it may eventually get bricked due to the way Pyth upgrades are implemented. (We only allows users to interact with the most recent package version for security reasons).
|
||||||
|
|
||||||
|
Therefore, you should build a [Sui programmable transaction](https://docs.sui.io/build/prog-trans-ts-sdk) that first updates the price by calling `pyth::update_single_price_feed` at the latest call-site from the client-side and then call a function in your contract that invokes `pyth::get_price` on the `PriceInfoObject` to get the recently updated price.
|
||||||
|
You can use `SuiPythClient` to build such transactions.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
|
||||||
|
import { TransactionBlock } from "@mysten/sui.js";
|
||||||
|
|
||||||
|
const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIds); // see quickstart section
|
||||||
|
|
||||||
|
|
||||||
|
// It is either injected from browser or instantiated in backend via some private key
|
||||||
|
const wallet: SignerWithProvider = getWallet();
|
||||||
|
// Get the state ids of the Pyth and Wormhole contracts from
|
||||||
|
// https://docs.pyth.network/documentation/pythnet-price-feeds/sui
|
||||||
|
const wormholeStateId = " 0xFILL_ME";
|
||||||
|
const pythStateId = "0xFILL_ME";
|
||||||
|
|
||||||
|
const client = new SuiPythClient(wallet.provider, pythStateId, wormholeStateId);
|
||||||
|
const tx = new TransactionBlock();
|
||||||
|
const priceInfoObjectIds = await client.updatePriceFeeds(tx, priceFeedUpdateData, priceIds);
|
||||||
|
|
||||||
|
tx.moveCall({
|
||||||
|
target: `YOUR_PACKAGE::YOUR_MODULE::use_pyth_for_defi`,
|
||||||
|
arguments: [
|
||||||
|
..., // other arguments needed for your contract
|
||||||
|
tx.object(pythStateId),
|
||||||
|
tx.object(priceInfoObjectIds[0]),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const txBlock = {
|
||||||
|
transactionBlock: tx,
|
||||||
|
options: {
|
||||||
|
showEffects: true,
|
||||||
|
showEvents: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await wallet.signAndExecuteTransactionBlock(txBlock);
|
||||||
|
```
|
||||||
|
|
||||||
|
Now in your contract you can consume the price by calling `pyth::get_price` or other utility functions on the `PriceInfoObject`.
|
||||||
|
|
||||||
|
### CLI Example
|
||||||
|
|
||||||
|
[This example](./src/examples/SuiRelay.ts) shows how to update prices on an Sui network. It does the following:
|
||||||
|
|
||||||
|
1. Fetches update data from the Price Service for the given price feeds.
|
||||||
|
2. Calls the Pyth Sui contract with the update data.
|
||||||
|
|
||||||
|
You can run this example with `npm run example-relay`. A full command that updates prices on Sui testnet looks like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SUI_KEY=YOUR_PRIV_KEY;
|
||||||
|
npm run example-relay -- --feed-id "5a035d5440f5c163069af66062bac6c79377bf88396fa27e6067bfca8096d280" \
|
||||||
|
--price-service "https://hermes-beta.pyth.network" \
|
||||||
|
--full-node "https://fullnode.testnet.sui.io:443" \
|
||||||
|
--pyth-state-id "0xd3e79c2c083b934e78b3bd58a490ec6b092561954da6e7322e1e2b3c8abfddc0" \
|
||||||
|
--wormhole-state-id "0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Off-chain prices
|
||||||
|
|
||||||
|
Many applications additionally need to display Pyth prices off-chain, for example, in their frontend application.
|
||||||
|
The `SuiPriceServiceConnection` provides two different ways to fetch the current Pyth price.
|
||||||
|
The code blocks below assume that the `connection` and `priceIds` objects have been initialized as shown above.
|
||||||
|
The first method is a single-shot query:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// `getLatestPriceFeeds` returns a `PriceFeed` for each price id. It contains all information about a price and has
|
||||||
|
// utility functions to get the current and exponentially-weighted moving average price, and other functionality.
|
||||||
|
const priceFeeds = await connection.getLatestPriceFeeds(priceIds);
|
||||||
|
// Get the price if it is not older than 60 seconds from the current time.
|
||||||
|
console.log(priceFeeds[0].getPriceNoOlderThan(60)); // Price { conf: '1234', expo: -8, price: '12345678' }
|
||||||
|
// Get the exponentially-weighted moving average price if it is not older than 60 seconds from the current time.
|
||||||
|
console.log(priceFeeds[1].getEmaPriceNoOlderThan(60));
|
||||||
|
```
|
||||||
|
|
||||||
|
The object also supports a streaming websocket connection that allows you to subscribe to every new price update for a given feed.
|
||||||
|
This method is useful if you want to show continuously updating real-time prices in your frontend:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Subscribe to the price feeds given by `priceId`. The callback will be invoked every time the requested feed
|
||||||
|
// gets a price update.
|
||||||
|
connection.subscribePriceFeedUpdates(priceIds, (priceFeed) => {
|
||||||
|
console.log(
|
||||||
|
`Received update for ${priceFeed.id}: ${priceFeed.getPriceNoOlderThan(60)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When using the subscription, make sure to close the websocket upon termination to finish the process gracefully.
|
||||||
|
setTimeout(() => {
|
||||||
|
connection.closeWebSocket();
|
||||||
|
}, 60000);
|
||||||
|
```
|
||||||
|
|
||||||
|
## [Price Service endpoints](https://docs.pyth.network/documentation/pythnet-price-feeds/price-service#public-endpoints)
|
|
@ -0,0 +1,5 @@
|
||||||
|
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: "ts-jest",
|
||||||
|
testEnvironment: "node",
|
||||||
|
};
|
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
"name": "@pythnetwork/pyth-sui-js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Pyth Network Sui Utilities",
|
||||||
|
"homepage": "https://pyth.network",
|
||||||
|
"author": {
|
||||||
|
"name": "Pyth Data Association"
|
||||||
|
},
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"lib/**/*"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/pyth-network/pyth-crosschain",
|
||||||
|
"directory": "target_chains/sui/sdk/js"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest --passWithNoTests",
|
||||||
|
"build": "tsc",
|
||||||
|
"example-relay": "npm run build && node lib/examples/SuiRelay.js",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\"",
|
||||||
|
"lint": "eslint src/",
|
||||||
|
"prepublishOnly": "npm run build && npm test && npm run lint",
|
||||||
|
"preversion": "npm run lint",
|
||||||
|
"version": "npm run format && git add -A src"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"pyth",
|
||||||
|
"oracle",
|
||||||
|
"sui"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@truffle/hdwallet-provider": "^2.1.5",
|
||||||
|
"@types/ethereum-protocol": "^1.0.2",
|
||||||
|
"@types/jest": "^29.4.0",
|
||||||
|
"@types/node": "^18.11.18",
|
||||||
|
"@types/web3-provider-engine": "^14.0.1",
|
||||||
|
"@types/yargs": "^17.0.20",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||||
|
"@typescript-eslint/parser": "^5.21.0",
|
||||||
|
"eslint": "^8.14.0",
|
||||||
|
"jest": "^29.4.1",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"ts-jest": "^29.0.5",
|
||||||
|
"typescript": "^4.6.3",
|
||||||
|
"web3": "^1.8.2",
|
||||||
|
"yargs": "^17.0.20"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@pythnetwork/price-service-client": "*",
|
||||||
|
"@mysten/sui.js": "^0.32.2",
|
||||||
|
"buffer": "^6.0.3"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import {
|
||||||
|
PriceServiceConnection,
|
||||||
|
HexString,
|
||||||
|
} from "@pythnetwork/price-service-client";
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
|
export class SuiPriceServiceConnection extends PriceServiceConnection {
|
||||||
|
/**
|
||||||
|
* Gets price update data (either batch price attestation VAAs or accumulator messages, depending on the chosen endpoint), 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 buffers containing the price update data.
|
||||||
|
*/
|
||||||
|
async getPriceFeedsUpdateData(priceIds: HexString[]): Promise<Buffer[]> {
|
||||||
|
// Fetch the latest price feed update VAAs from the price service
|
||||||
|
const latestVaas = await this.getLatestVaas(priceIds);
|
||||||
|
return latestVaas.map((vaa) => Buffer.from(vaa, "base64"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,313 @@
|
||||||
|
import {
|
||||||
|
JsonRpcProvider,
|
||||||
|
ObjectId,
|
||||||
|
SUI_CLOCK_OBJECT_ID,
|
||||||
|
TransactionBlock,
|
||||||
|
} from "@mysten/sui.js";
|
||||||
|
import { HexString } from "@pythnetwork/price-service-client";
|
||||||
|
|
||||||
|
export class SuiPythClient {
|
||||||
|
private pythPackageId: ObjectId | undefined;
|
||||||
|
private wormholePackageId: ObjectId | undefined;
|
||||||
|
private priceTableId: ObjectId | undefined;
|
||||||
|
private priceFeedObjectIdCache: Map<HexString, ObjectId> = new Map();
|
||||||
|
private baseUpdateFee: number | undefined;
|
||||||
|
constructor(
|
||||||
|
public provider: JsonRpcProvider,
|
||||||
|
public pythStateId: ObjectId,
|
||||||
|
public wormholeStateId: ObjectId
|
||||||
|
) {
|
||||||
|
this.pythPackageId = undefined;
|
||||||
|
this.wormholePackageId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseUpdateFee(): Promise<number> {
|
||||||
|
if (this.baseUpdateFee === undefined) {
|
||||||
|
const result = await this.provider.getObject({
|
||||||
|
id: this.pythStateId,
|
||||||
|
options: { showContent: true },
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
!result.data ||
|
||||||
|
!result.data.content ||
|
||||||
|
result.data.content.dataType !== "moveObject"
|
||||||
|
)
|
||||||
|
throw new Error("Unable to fetch pyth state object");
|
||||||
|
this.baseUpdateFee = result.data.content.fields.base_update_fee as number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.baseUpdateFee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getPackageId returns the latest package id that the object belongs to. Use this to
|
||||||
|
* fetch the latest package id for a given object id and handle package upgrades automatically.
|
||||||
|
* @param objectId
|
||||||
|
* @returns package id
|
||||||
|
*/
|
||||||
|
async getPackageId(objectId: ObjectId): Promise<ObjectId> {
|
||||||
|
const state = await this.provider
|
||||||
|
.getObject({
|
||||||
|
id: objectId,
|
||||||
|
options: {
|
||||||
|
showContent: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
if (result.data?.content?.dataType == "moveObject") {
|
||||||
|
return result.data.content.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("not move object");
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("upgrade_cap" in state) {
|
||||||
|
return state.upgrade_cap.fields.package;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("upgrade_cap not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the commands for calling wormhole and verifying the vaas and returns the verified vaas.
|
||||||
|
* @param vaas array of vaas to verify
|
||||||
|
* @param tx transaction block to add commands to
|
||||||
|
*/
|
||||||
|
async verifyVaas(vaas: Buffer[], tx: TransactionBlock) {
|
||||||
|
const wormholePackageId = await this.getWormholePackageId();
|
||||||
|
const verifiedVaas = [];
|
||||||
|
for (const vaa of vaas) {
|
||||||
|
const [verifiedVaa] = tx.moveCall({
|
||||||
|
target: `${wormholePackageId}::vaa::parse_and_verify`,
|
||||||
|
arguments: [
|
||||||
|
tx.object(this.wormholeStateId),
|
||||||
|
tx.pure(Array.from(vaa)),
|
||||||
|
tx.object(SUI_CLOCK_OBJECT_ID),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
verifiedVaas.push(verifiedVaa);
|
||||||
|
}
|
||||||
|
return verifiedVaas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the necessary commands for updating the pyth price feeds to the transaction block.
|
||||||
|
* @param tx transaction block to add commands to
|
||||||
|
* @param updates array of price feed updates received from the price service
|
||||||
|
* @param feedIds array of feed ids to update (in hex format)
|
||||||
|
*/
|
||||||
|
async updatePriceFeeds(
|
||||||
|
tx: TransactionBlock,
|
||||||
|
updates: Buffer[],
|
||||||
|
feedIds: HexString[]
|
||||||
|
): Promise<ObjectId[]> {
|
||||||
|
const wormholePackageId = await this.getWormholePackageId();
|
||||||
|
const packageId = await this.getPythPackageId();
|
||||||
|
|
||||||
|
let priceUpdatesHotPotato;
|
||||||
|
if (updates.every((update) => this.isAccumulatorMsg(update))) {
|
||||||
|
if (updates.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
"SDK does not support sending multiple accumulator messages in a single transaction"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const vaa = this.extractVaaBytesFromAccumulatorMessage(updates[0]);
|
||||||
|
const verifiedVaas = await this.verifyVaas([vaa], tx);
|
||||||
|
[priceUpdatesHotPotato] = tx.moveCall({
|
||||||
|
target: `${packageId}::pyth::create_authenticated_price_infos_using_accumulator`,
|
||||||
|
arguments: [
|
||||||
|
tx.object(this.pythStateId),
|
||||||
|
tx.pure(Array.from(updates[0])),
|
||||||
|
verifiedVaas[0],
|
||||||
|
tx.object(SUI_CLOCK_OBJECT_ID),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else if (updates.every((vaa) => !this.isAccumulatorMsg(vaa))) {
|
||||||
|
const verifiedVaas = await this.verifyVaas(updates, tx);
|
||||||
|
[priceUpdatesHotPotato] = tx.moveCall({
|
||||||
|
target: `${packageId}::pyth::create_price_infos_hot_potato`,
|
||||||
|
arguments: [
|
||||||
|
tx.object(this.pythStateId),
|
||||||
|
tx.makeMoveVec({
|
||||||
|
type: `${wormholePackageId}::vaa::VAA`,
|
||||||
|
objects: verifiedVaas,
|
||||||
|
}),
|
||||||
|
tx.object(SUI_CLOCK_OBJECT_ID),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error("Can't mix accumulator and non-accumulator messages");
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceInfoObjects: ObjectId[] = [];
|
||||||
|
for (const feedId of feedIds) {
|
||||||
|
const priceInfoObjectId = await this.getPriceFeedObjectId(feedId);
|
||||||
|
if (!priceInfoObjectId) {
|
||||||
|
throw new Error(
|
||||||
|
`Price feed ${feedId} not found, please create it first`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
priceInfoObjects.push(priceInfoObjectId);
|
||||||
|
const coin = tx.splitCoins(tx.gas, [
|
||||||
|
tx.pure(await this.getBaseUpdateFee()),
|
||||||
|
]);
|
||||||
|
[priceUpdatesHotPotato] = tx.moveCall({
|
||||||
|
target: `${packageId}::pyth::update_single_price_feed`,
|
||||||
|
arguments: [
|
||||||
|
tx.object(this.pythStateId),
|
||||||
|
priceUpdatesHotPotato,
|
||||||
|
tx.object(priceInfoObjectId),
|
||||||
|
coin,
|
||||||
|
tx.object(SUI_CLOCK_OBJECT_ID),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tx.moveCall({
|
||||||
|
target: `${packageId}::hot_potato_vector::destroy`,
|
||||||
|
arguments: [priceUpdatesHotPotato],
|
||||||
|
typeArguments: [`${packageId}::price_info::PriceInfo`],
|
||||||
|
});
|
||||||
|
return priceInfoObjects;
|
||||||
|
}
|
||||||
|
async createPriceFeed(tx: TransactionBlock, updates: Buffer[]) {
|
||||||
|
const wormholePackageId = await this.getWormholePackageId();
|
||||||
|
const packageId = await this.getPythPackageId();
|
||||||
|
if (updates.every((update) => this.isAccumulatorMsg(update))) {
|
||||||
|
if (updates.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
"SDK does not support sending multiple accumulator messages in a single transaction"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const vaa = this.extractVaaBytesFromAccumulatorMessage(updates[0]);
|
||||||
|
const verifiedVaas = await this.verifyVaas([vaa], tx);
|
||||||
|
tx.moveCall({
|
||||||
|
target: `${packageId}::pyth::create_price_feeds_using_accumulator`,
|
||||||
|
arguments: [
|
||||||
|
tx.object(this.pythStateId),
|
||||||
|
tx.pure(Array.from(updates[0])),
|
||||||
|
verifiedVaas[0],
|
||||||
|
tx.object(SUI_CLOCK_OBJECT_ID),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else if (updates.every((vaa) => !this.isAccumulatorMsg(vaa))) {
|
||||||
|
const verifiedVaas = await this.verifyVaas(updates, tx);
|
||||||
|
tx.moveCall({
|
||||||
|
target: `${packageId}::pyth::create_price_feeds`,
|
||||||
|
arguments: [
|
||||||
|
tx.object(this.pythStateId),
|
||||||
|
tx.makeMoveVec({
|
||||||
|
type: `${wormholePackageId}::vaa::VAA`,
|
||||||
|
objects: verifiedVaas,
|
||||||
|
}),
|
||||||
|
tx.object(SUI_CLOCK_OBJECT_ID),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error("Can't mix accumulator and non-accumulator messages");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the packageId for the wormhole package if not already cached
|
||||||
|
*/
|
||||||
|
async getWormholePackageId() {
|
||||||
|
if (!this.wormholePackageId) {
|
||||||
|
this.wormholePackageId = await this.getPackageId(this.wormholeStateId);
|
||||||
|
}
|
||||||
|
return this.wormholePackageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the packageId for the pyth package if not already cached
|
||||||
|
*/
|
||||||
|
async getPythPackageId() {
|
||||||
|
if (!this.pythPackageId) {
|
||||||
|
this.pythPackageId = await this.getPackageId(this.pythStateId);
|
||||||
|
}
|
||||||
|
return this.pythPackageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the priceFeedObjectId for a given feedId if not already cached
|
||||||
|
* @param feedId
|
||||||
|
*/
|
||||||
|
async getPriceFeedObjectId(feedId: HexString): Promise<ObjectId | undefined> {
|
||||||
|
const normalizedFeedId = feedId.replace("0x", "");
|
||||||
|
if (!this.priceFeedObjectIdCache.has(normalizedFeedId)) {
|
||||||
|
const tableId = await this.getPriceTableId();
|
||||||
|
const result = await this.provider.getDynamicFieldObject({
|
||||||
|
parentId: tableId,
|
||||||
|
name: {
|
||||||
|
type: `${await this.getPythPackageId()}::price_identifier::PriceIdentifier`,
|
||||||
|
value: {
|
||||||
|
bytes: Array.from(Buffer.from(normalizedFeedId, "hex")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!result.data || !result.data.content) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (result.data.content.dataType !== "moveObject") {
|
||||||
|
throw new Error("Price feed type mismatch");
|
||||||
|
}
|
||||||
|
this.priceFeedObjectIdCache.set(
|
||||||
|
normalizedFeedId,
|
||||||
|
result.data.content.fields.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.priceFeedObjectIdCache.get(normalizedFeedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the price table object id for the current state id if not cached
|
||||||
|
* @returns price table object id
|
||||||
|
*/
|
||||||
|
async getPriceTableId(): Promise<ObjectId> {
|
||||||
|
if (this.priceTableId === undefined) {
|
||||||
|
const result = await this.provider.getDynamicFieldObject({
|
||||||
|
parentId: this.pythStateId,
|
||||||
|
name: {
|
||||||
|
type: "vector<u8>",
|
||||||
|
value: "price_info",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!result.data) {
|
||||||
|
throw new Error(
|
||||||
|
"Price Table not found, contract may not be initialized"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.priceTableId = result.data.objectId;
|
||||||
|
}
|
||||||
|
return this.priceTableId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a message is an accumulator message or not
|
||||||
|
* @param msg - update message from price service
|
||||||
|
*/
|
||||||
|
isAccumulatorMsg(msg: Buffer) {
|
||||||
|
const ACCUMULATOR_MAGIC = "504e4155";
|
||||||
|
return msg.toString("hex").slice(0, 8) === ACCUMULATOR_MAGIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the vaa bytes embedded in an accumulator message.
|
||||||
|
* @param accumulatorMessage - the accumulator price update message
|
||||||
|
* @returns vaa bytes as a uint8 array
|
||||||
|
*/
|
||||||
|
extractVaaBytesFromAccumulatorMessage(accumulatorMessage: Buffer): Buffer {
|
||||||
|
if (!this.isAccumulatorMsg(accumulatorMessage)) {
|
||||||
|
throw new Error("Not an accumulator message");
|
||||||
|
}
|
||||||
|
// the first 6 bytes in the accumulator message encode the header, major, and minor bytes
|
||||||
|
// we ignore them, since we are only interested in the VAA bytes
|
||||||
|
const trailingPayloadSize = accumulatorMessage.readUint8(6);
|
||||||
|
const vaaSizeOffset =
|
||||||
|
7 + // header bytes (header(4) + major(1) + minor(1) + trailing payload size(1))
|
||||||
|
trailingPayloadSize + // trailing payload (variable number of bytes)
|
||||||
|
1; // proof_type (1 byte)
|
||||||
|
const vaaSize = accumulatorMessage.readUint16BE(vaaSizeOffset);
|
||||||
|
const vaaOffset = vaaSizeOffset + 2;
|
||||||
|
return accumulatorMessage.subarray(vaaOffset, vaaOffset + vaaSize);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
import yargs from "yargs";
|
||||||
|
import { hideBin } from "yargs/helpers";
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
Ed25519Keypair,
|
||||||
|
JsonRpcProvider,
|
||||||
|
RawSigner,
|
||||||
|
TransactionBlock,
|
||||||
|
} from "@mysten/sui.js";
|
||||||
|
|
||||||
|
import { SuiPythClient } from "../client";
|
||||||
|
import { SuiPriceServiceConnection } from "../index";
|
||||||
|
|
||||||
|
const argvPromise = yargs(hideBin(process.argv))
|
||||||
|
.option("feed-id", {
|
||||||
|
description:
|
||||||
|
"Price feed ids to update without the leading 0x (e.g f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b). Can be provided multiple times for multiple feed updates",
|
||||||
|
type: "array",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("price-service", {
|
||||||
|
description:
|
||||||
|
"Endpoint URL for the price service. e.g: https://xc-mainnet.pyth.network",
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("full-node", {
|
||||||
|
description:
|
||||||
|
"URL of the full Sui node RPC endpoint. e.g: https://fullnode.testnet.sui.io:443",
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("pyth-state-id", {
|
||||||
|
description: "Pyth state object id.",
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("wormhole-state-id", {
|
||||||
|
description: "Wormhole state object id.",
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
}).argv;
|
||||||
|
|
||||||
|
export function getProvider(url: string) {
|
||||||
|
return new JsonRpcProvider(new Connection({ fullnode: url }));
|
||||||
|
}
|
||||||
|
async function run() {
|
||||||
|
if (process.env.SUI_KEY === undefined) {
|
||||||
|
throw new Error(`SUI_KEY environment variable should be set.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const argv = await argvPromise;
|
||||||
|
|
||||||
|
// Fetch the latest price feed update data from the Price Service
|
||||||
|
const connection = new SuiPriceServiceConnection(argv["price-service"]);
|
||||||
|
const feeds = argv["feed-id"] as string[];
|
||||||
|
const priceFeedUpdateData = await connection.getPriceFeedsUpdateData(feeds);
|
||||||
|
|
||||||
|
const provider = getProvider(argv["full-node"]);
|
||||||
|
const wormholeStateId = argv["wormhole-state-id"];
|
||||||
|
const pythStateId = argv["pyth-state-id"];
|
||||||
|
|
||||||
|
const client = new SuiPythClient(provider, pythStateId, wormholeStateId);
|
||||||
|
const tx = new TransactionBlock();
|
||||||
|
await client.updatePriceFeeds(tx, priceFeedUpdateData, feeds);
|
||||||
|
|
||||||
|
const wallet = new RawSigner(
|
||||||
|
Ed25519Keypair.fromSecretKey(Buffer.from(process.env.SUI_KEY, "hex")),
|
||||||
|
provider
|
||||||
|
);
|
||||||
|
|
||||||
|
const txBlock = {
|
||||||
|
transactionBlock: tx,
|
||||||
|
options: {
|
||||||
|
showEffects: true,
|
||||||
|
showEvents: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = await wallet.signAndExecuteTransactionBlock(txBlock);
|
||||||
|
console.dir(result, { depth: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
|
@ -0,0 +1,11 @@
|
||||||
|
export { SuiPriceServiceConnection } from "./SuiPriceServiceConnection";
|
||||||
|
export { SuiPythClient } from "./client";
|
||||||
|
|
||||||
|
export {
|
||||||
|
DurationInMs,
|
||||||
|
HexString,
|
||||||
|
Price,
|
||||||
|
PriceFeed,
|
||||||
|
PriceServiceConnectionConfig,
|
||||||
|
UnixTimestamp,
|
||||||
|
} from "@pythnetwork/price-service-client";
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "./lib",
|
||||||
|
"rootDir": "src/",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "**/__tests__/*"]
|
||||||
|
}
|
Loading…
Reference in New Issue