@pythnetwork/price-pusher 5.3.0 → 5.4.4
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.
- package/README.md +24 -21
- package/lib/aptos/aptos.d.ts +5 -2
- package/lib/aptos/aptos.d.ts.map +1 -0
- package/lib/aptos/aptos.js +49 -39
- package/lib/aptos/command.d.ts +1 -0
- package/lib/aptos/command.d.ts.map +1 -0
- package/lib/aptos/command.js +3 -0
- package/lib/common.d.ts +1 -0
- package/lib/common.d.ts.map +1 -0
- package/lib/controller.d.ts +2 -1
- package/lib/controller.d.ts.map +1 -0
- package/lib/controller.js +10 -2
- package/lib/evm/command.d.ts +1 -0
- package/lib/evm/command.d.ts.map +1 -0
- package/lib/evm/command.js +3 -0
- package/lib/evm/custom-gas-station.d.ts +1 -0
- package/lib/evm/custom-gas-station.d.ts.map +1 -0
- package/lib/evm/evm.d.ts +1 -0
- package/lib/evm/evm.d.ts.map +1 -0
- package/lib/evm/evm.js +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/injective/command.d.ts +1 -0
- package/lib/injective/command.d.ts.map +1 -0
- package/lib/injective/injective.d.ts +2 -0
- package/lib/injective/injective.d.ts.map +1 -0
- package/lib/injective/injective.js +32 -31
- package/lib/interface.d.ts +1 -0
- package/lib/interface.d.ts.map +1 -0
- package/lib/options.d.ts +1 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/price-config.d.ts +10 -1
- package/lib/price-config.d.ts.map +1 -0
- package/lib/price-config.js +39 -10
- package/lib/pyth-price-listener.d.ts +1 -0
- package/lib/pyth-price-listener.d.ts.map +1 -0
- package/lib/sui/command.d.ts +5 -1
- package/lib/sui/command.d.ts.map +1 -0
- package/lib/sui/command.js +28 -3
- package/lib/sui/sui.d.ts +21 -2
- package/lib/sui/sui.d.ts.map +1 -0
- package/lib/sui/sui.js +181 -12
- package/lib/utils.d.ts +1 -0
- package/lib/utils.d.ts.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -37,9 +37,20 @@ The parameters above are configured per price feed in a price configuration YAML
|
|
|
37
37
|
time_difference: 60 # Time difference threshold (in seconds) to push a newer price feed.
|
|
38
38
|
price_deviation: 0.5 # The price deviation (%) threshold to push a newer price feed.
|
|
39
39
|
confidence_ratio: 1 # The confidence/price (%) threshold to push a newer price feed.
|
|
40
|
+
# Optional block to configure whether this feed can be early updated. If at least one feed meets the
|
|
41
|
+
# triggering conditions above, all other feeds who meet the early update conditions will be included in
|
|
42
|
+
# the submitted batch of prices. This logic takes advantage of the fact that adding a feed to a larger
|
|
43
|
+
# batch of updates incurs a minimal gas cost. All fields below are optional (and interpreted as infinity if omitted)
|
|
44
|
+
# and have the same semantics as the corresponding fields above.
|
|
45
|
+
early_update:
|
|
46
|
+
time_difference: 30
|
|
47
|
+
price_deviation: 0.1
|
|
48
|
+
confidence_ratio: 0.5
|
|
40
49
|
- ...
|
|
41
50
|
```
|
|
42
51
|
|
|
52
|
+
Two sample YAML configuration files are available in the root of this repo.
|
|
53
|
+
|
|
43
54
|
You can get the list of available price feeds from
|
|
44
55
|
[here](https://pyth.network/developers/price-feed-ids/).
|
|
45
56
|
|
|
@@ -57,7 +68,7 @@ cd price_pusher
|
|
|
57
68
|
npm run start -- evm --endpoint wss://example-rpc.com \
|
|
58
69
|
--pyth-contract-address 0xff1a0f4744e8582DF...... \
|
|
59
70
|
--price-service-endpoint https://example-pyth-price.com \
|
|
60
|
-
--price-config-file "path/to/price-config
|
|
71
|
+
--price-config-file "path/to/price-config.testnet.sample.yaml" \
|
|
61
72
|
--mnemonic-file "path/to/mnemonic.txt" \
|
|
62
73
|
[--pushing-frequency 10] \
|
|
63
74
|
[--polling-frequency 5] \
|
|
@@ -66,7 +77,7 @@ npm run start -- evm --endpoint wss://example-rpc.com \
|
|
|
66
77
|
# For Injective
|
|
67
78
|
npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
|
|
68
79
|
--pyth-contract-address inj1z60tg0... --price-service-endpoint "https://example-pyth-price.com" \
|
|
69
|
-
--price-config-file "path/to/price-config
|
|
80
|
+
--price-config-file "path/to/price-config.testnet.sample.yaml" \
|
|
70
81
|
--mnemonic-file "path/to/mnemonic.txt" \
|
|
71
82
|
[--pushing-frequency 10] \
|
|
72
83
|
[--polling-frequency 5] \
|
|
@@ -80,27 +91,19 @@ npm run start -- aptos --endpoint https://fullnode.testnet.aptoslabs.com/v1 \
|
|
|
80
91
|
[--polling-frequency 5] \
|
|
81
92
|
|
|
82
93
|
# For Sui
|
|
83
|
-
npm run start -- sui
|
|
84
|
-
--endpoint https://sui-testnet-rpc.allthatnode.com
|
|
85
|
-
--pyth-package-id 0x975e063f398f720af4f33ec06a927f14ea76ca24f7f8dd544aa62ab9d5d15f44
|
|
86
|
-
--pyth-state-id 0xd8afde3a48b4ff7212bd6829a150f43f59043221200d63504d981f62bff2e27a
|
|
87
|
-
--wormhole-package-id 0xcc029e2810f17f9f43f52262f40026a71fbdca40ed3803ad2884994361910b7e
|
|
88
|
-
--wormhole-state-id 0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02
|
|
89
|
-
--price-feed-to-price-info-object-table-id 0xf8929174008c662266a1adde78e1e8e33016eb7ad37d379481e860b911e40ed5
|
|
90
|
-
--price-service-endpoint https://xc-testnet.pyth.network
|
|
91
|
-
--mnemonic-file ./mnemonic
|
|
92
|
-
--price-config-file ./price-config.testnet.sample.yaml
|
|
94
|
+
npm run start -- sui \
|
|
95
|
+
--endpoint https://sui-testnet-rpc.allthatnode.com \
|
|
96
|
+
--pyth-package-id 0x975e063f398f720af4f33ec06a927f14ea76ca24f7f8dd544aa62ab9d5d15f44 \
|
|
97
|
+
--pyth-state-id 0xd8afde3a48b4ff7212bd6829a150f43f59043221200d63504d981f62bff2e27a \
|
|
98
|
+
--wormhole-package-id 0xcc029e2810f17f9f43f52262f40026a71fbdca40ed3803ad2884994361910b7e \
|
|
99
|
+
--wormhole-state-id 0xebba4cc4d614f7a7cdbe883acc76d1cc767922bc96778e7b68be0d15fce27c02 \
|
|
100
|
+
--price-feed-to-price-info-object-table-id 0xf8929174008c662266a1adde78e1e8e33016eb7ad37d379481e860b911e40ed5 \
|
|
101
|
+
--price-service-endpoint https://xc-testnet.pyth.network \
|
|
102
|
+
--mnemonic-file ./mnemonic \
|
|
103
|
+
--price-config-file ./price-config.testnet.sample.yaml \
|
|
93
104
|
[--pushing-frequency 10] \
|
|
94
105
|
[--polling-frequency 5] \
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
--endpoint https://fullnode.testnet.aptoslabs.com/v1 \
|
|
99
|
-
--pyth-contract-address 0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387 --price-service-endpoint "https://xc-testnet.pyth.network" \
|
|
100
|
-
--price-config-file "./price-config.testnet.sample.yaml" \
|
|
101
|
-
--mnemonic-file "path/to/mnemonic.txt" \
|
|
102
|
-
[--pushing-frequency 10] \
|
|
103
|
-
[--polling-frequency 5] \
|
|
106
|
+
[--num-gas-objects 30]
|
|
104
107
|
|
|
105
108
|
|
|
106
109
|
# Or, run the price pusher docker image instead of building from the source
|
package/lib/aptos/aptos.d.ts
CHANGED
|
@@ -9,14 +9,15 @@ export declare class AptosPriceListener extends ChainPriceListener {
|
|
|
9
9
|
});
|
|
10
10
|
getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined>;
|
|
11
11
|
}
|
|
12
|
+
export declare const APTOS_ACCOUNT_HD_PATH = "m/44'/637'/0'/0'/0'";
|
|
12
13
|
export declare class AptosPricePusher implements IPricePusher {
|
|
13
14
|
private priceServiceConnection;
|
|
14
15
|
private pythContractAddress;
|
|
15
16
|
private endpoint;
|
|
16
17
|
private mnemonic;
|
|
17
18
|
private overrideGasPriceMultiplier;
|
|
18
|
-
private
|
|
19
|
-
private
|
|
19
|
+
private lastSequenceNumber;
|
|
20
|
+
private sequenceNumberLocked;
|
|
20
21
|
constructor(priceServiceConnection: PriceServiceConnection, pythContractAddress: string, endpoint: string, mnemonic: string, overrideGasPriceMultiplier: number);
|
|
21
22
|
/**
|
|
22
23
|
* Gets price update data which then can be submitted to the Pyth contract to update the prices.
|
|
@@ -27,4 +28,6 @@ export declare class AptosPricePusher implements IPricePusher {
|
|
|
27
28
|
*/
|
|
28
29
|
getPriceFeedsUpdateData(priceIds: string[]): Promise<number[][]>;
|
|
29
30
|
updatePriceFeed(priceIds: string[], pubTimesToPush: number[]): Promise<void>;
|
|
31
|
+
private tryGetNextSequenceNumber;
|
|
30
32
|
}
|
|
33
|
+
//# sourceMappingURL=aptos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aptos.d.ts","sourceRoot":"","sources":["../../src/aptos/aptos.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,qBAAa,kBAAmB,SAAQ,kBAAkB;IAEtD,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,QAAQ;gBADR,UAAU,EAAE,MAAM,EAClB,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;CA6C3E;AAGD,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAC3D,qBAAa,gBAAiB,YAAW,YAAY;IAOjD,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,0BAA0B;IATpC,OAAO,CAAC,kBAAkB,CAAqB;IAE/C,OAAO,CAAC,oBAAoB,CAAU;gBAG5B,sBAAsB,EAAE,sBAAsB,EAC9C,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,0BAA0B,EAAE,MAAM;IAK5C;;;;;;OAMG;IACG,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAQhE,eAAe,CACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAAC,IAAI,CAAC;YA4DF,wBAAwB;CA+BvC"}
|
package/lib/aptos/aptos.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AptosPricePusher = exports.AptosPriceListener = void 0;
|
|
3
|
+
exports.AptosPricePusher = exports.APTOS_ACCOUNT_HD_PATH = exports.AptosPriceListener = void 0;
|
|
4
4
|
const interface_1 = require("../interface");
|
|
5
5
|
const aptos_1 = require("aptos");
|
|
6
6
|
class AptosPriceListener extends interface_1.ChainPriceListener {
|
|
@@ -42,20 +42,25 @@ class AptosPriceListener extends interface_1.ChainPriceListener {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
exports.AptosPriceListener = AptosPriceListener;
|
|
45
|
+
// Derivation path for aptos accounts
|
|
46
|
+
exports.APTOS_ACCOUNT_HD_PATH = "m/44'/637'/0'/0'/0'";
|
|
45
47
|
class AptosPricePusher {
|
|
46
48
|
priceServiceConnection;
|
|
47
49
|
pythContractAddress;
|
|
48
50
|
endpoint;
|
|
49
51
|
mnemonic;
|
|
50
52
|
overrideGasPriceMultiplier;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
// The last sequence number that has a transaction submitted.
|
|
54
|
+
lastSequenceNumber;
|
|
55
|
+
// If true, we are trying to fetch the most recent sequence number from the blockchain.
|
|
56
|
+
sequenceNumberLocked;
|
|
53
57
|
constructor(priceServiceConnection, pythContractAddress, endpoint, mnemonic, overrideGasPriceMultiplier) {
|
|
54
58
|
this.priceServiceConnection = priceServiceConnection;
|
|
55
59
|
this.pythContractAddress = pythContractAddress;
|
|
56
60
|
this.endpoint = endpoint;
|
|
57
61
|
this.mnemonic = mnemonic;
|
|
58
62
|
this.overrideGasPriceMultiplier = overrideGasPriceMultiplier;
|
|
63
|
+
this.sequenceNumberLocked = false;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* Gets price update data which then can be submitted to the Pyth contract to update the prices.
|
|
@@ -86,54 +91,59 @@ class AptosPricePusher {
|
|
|
86
91
|
return;
|
|
87
92
|
}
|
|
88
93
|
try {
|
|
89
|
-
const account = aptos_1.AptosAccount.fromDerivePath(
|
|
94
|
+
const account = aptos_1.AptosAccount.fromDerivePath(exports.APTOS_ACCOUNT_HD_PATH, this.mnemonic);
|
|
90
95
|
const client = new aptos_1.AptosClient(this.endpoint);
|
|
96
|
+
const sequenceNumber = await this.tryGetNextSequenceNumber(client, account);
|
|
91
97
|
const rawTx = await client.generateTransaction(account.address(), {
|
|
92
|
-
function: `${this.pythContractAddress}::pyth::
|
|
98
|
+
function: `${this.pythContractAddress}::pyth::update_price_feeds_with_funder`,
|
|
93
99
|
type_arguments: [],
|
|
94
|
-
arguments: [
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
pubTimesToPush,
|
|
98
|
-
],
|
|
100
|
+
arguments: [priceFeedUpdateData],
|
|
101
|
+
}, {
|
|
102
|
+
sequence_number: sequenceNumber.toFixed(),
|
|
99
103
|
});
|
|
100
|
-
const
|
|
101
|
-
estimateGasUnitPrice: true,
|
|
102
|
-
estimateMaxGasAmount: true,
|
|
103
|
-
estimatePrioritizedGasUnitPrice: true,
|
|
104
|
-
});
|
|
105
|
-
// Transactions on Aptos can be prioritized by paying a higher gas unit price.
|
|
106
|
-
// We are storing the gas unit price paid for the last transaction.
|
|
107
|
-
// If that transaction is not added to the block, we are increasing the gas unit price
|
|
108
|
-
// by multiplying the old gas unit price with `this.overrideGasPriceMultiplier`.
|
|
109
|
-
// After which we are sending a transaction with the same sequence number as the last
|
|
110
|
-
// transaction. Since they have the same sequence number only one of them will be added to
|
|
111
|
-
// the block and we won't be paying fees twice.
|
|
112
|
-
let gasUnitPrice = Number(simulation[0].gas_unit_price);
|
|
113
|
-
if (this.lastPushAttempt !== undefined &&
|
|
114
|
-
Number(simulation[0].sequence_number) === this.lastPushAttempt.nonce) {
|
|
115
|
-
const newGasUnitPrice = Number(this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier);
|
|
116
|
-
if (gasUnitPrice < newGasUnitPrice)
|
|
117
|
-
gasUnitPrice = newGasUnitPrice;
|
|
118
|
-
}
|
|
119
|
-
const gasUsed = Number(simulation[0].gas_used) * 1.5;
|
|
120
|
-
const maxGasAmount = Number(gasUnitPrice * gasUsed);
|
|
121
|
-
const rawTxWithFee = new aptos_1.TxnBuilderTypes.RawTransaction(rawTx.sender, rawTx.sequence_number, rawTx.payload, BigInt(maxGasAmount.toFixed()), BigInt(gasUnitPrice.toFixed()), rawTx.expiration_timestamp_secs, rawTx.chain_id);
|
|
122
|
-
const signedTx = await client.signTransaction(account, rawTxWithFee);
|
|
104
|
+
const signedTx = await client.signTransaction(account, rawTx);
|
|
123
105
|
const pendingTx = await client.submitTransaction(signedTx);
|
|
124
|
-
console.log("
|
|
125
|
-
// Update lastAttempt
|
|
126
|
-
this.lastPushAttempt = {
|
|
127
|
-
nonce: Number(pendingTx.sequence_number),
|
|
128
|
-
gasPrice: gasUnitPrice,
|
|
129
|
-
};
|
|
106
|
+
console.log("Successfully broadcasted txHash:", pendingTx.hash);
|
|
130
107
|
return;
|
|
131
108
|
}
|
|
132
109
|
catch (e) {
|
|
133
110
|
console.error("Error executing messages");
|
|
134
111
|
console.log(e);
|
|
112
|
+
// Reset the sequence number to re-sync it (in case that was the issue)
|
|
113
|
+
this.lastSequenceNumber = undefined;
|
|
135
114
|
return;
|
|
136
115
|
}
|
|
137
116
|
}
|
|
117
|
+
// Try to get the next sequence number for account. This function uses a local cache
|
|
118
|
+
// to predict the next sequence number if possible; if not, it fetches the number from
|
|
119
|
+
// the blockchain itself (and caches it for later).
|
|
120
|
+
async tryGetNextSequenceNumber(client, account) {
|
|
121
|
+
if (this.lastSequenceNumber !== undefined) {
|
|
122
|
+
this.lastSequenceNumber += 1;
|
|
123
|
+
return this.lastSequenceNumber;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Fetch from the blockchain if we don't have the local cache.
|
|
127
|
+
// Note that this is locked so that only 1 fetch occurs regardless of how many updates
|
|
128
|
+
// happen during that fetch.
|
|
129
|
+
if (!this.sequenceNumberLocked) {
|
|
130
|
+
try {
|
|
131
|
+
this.sequenceNumberLocked = true;
|
|
132
|
+
this.lastSequenceNumber = Number((await client.getAccount(account.address())).sequence_number);
|
|
133
|
+
console.log(`Fetched account sequence number: ${this.lastSequenceNumber}`);
|
|
134
|
+
return this.lastSequenceNumber;
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
throw new Error("Failed to retrieve sequence number");
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
this.sequenceNumberLocked = false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
throw new Error("Waiting for sequence number in another thread.");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
138
148
|
}
|
|
139
149
|
exports.AptosPricePusher = AptosPricePusher;
|
package/lib/aptos/command.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/aptos/command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;oBAkCL,GAAG;;AA1B9B,wBA2FE"}
|
package/lib/aptos/command.js
CHANGED
|
@@ -33,6 +33,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
33
33
|
const pyth_price_listener_1 = require("../pyth-price-listener");
|
|
34
34
|
const controller_1 = require("../controller");
|
|
35
35
|
const aptos_1 = require("./aptos");
|
|
36
|
+
const aptos_2 = require("aptos");
|
|
36
37
|
exports.default = {
|
|
37
38
|
command: "aptos",
|
|
38
39
|
describe: "run price pusher for aptos",
|
|
@@ -72,6 +73,8 @@ exports.default = {
|
|
|
72
73
|
},
|
|
73
74
|
});
|
|
74
75
|
const mnemonic = fs_1.default.readFileSync(mnemonicFile, "utf-8").trim();
|
|
76
|
+
const account = aptos_2.AptosAccount.fromDerivePath(aptos_1.APTOS_ACCOUNT_HD_PATH, mnemonic);
|
|
77
|
+
console.log(`Pushing from account address: ${account.address()}`);
|
|
75
78
|
const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
|
|
76
79
|
const pythListener = new pyth_price_listener_1.PythPriceListener(priceServiceConnection, priceItems);
|
|
77
80
|
const aptosListener = new aptos_1.AptosPriceListener(pythContractAddress, endpoint, priceItems, { pollingFrequency });
|
package/lib/common.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC"}
|
package/lib/controller.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DurationInSeconds } from "./utils";
|
|
2
|
-
import {
|
|
2
|
+
import { IPriceListener, IPricePusher } from "./interface";
|
|
3
3
|
import { PriceConfig } from "./price-config";
|
|
4
4
|
export declare class Controller {
|
|
5
5
|
private priceConfigs;
|
|
@@ -12,3 +12,4 @@ export declare class Controller {
|
|
|
12
12
|
});
|
|
13
13
|
start(): Promise<void>;
|
|
14
14
|
}
|
|
15
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAS,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAiC,MAAM,gBAAgB,CAAC;AAE5E,qBAAa,UAAU;IAGnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,sBAAsB;IALhC,OAAO,CAAC,gBAAgB,CAAoB;gBAElC,YAAY,EAAE,WAAW,EAAE,EAC3B,mBAAmB,EAAE,cAAc,EACnC,mBAAmB,EAAE,cAAc,EACnC,sBAAsB,EAAE,YAAY,EAC5C,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAKG,KAAK;CA2DZ"}
|
package/lib/controller.js
CHANGED
|
@@ -25,18 +25,26 @@ class Controller {
|
|
|
25
25
|
// their might be a message sent before.
|
|
26
26
|
await (0, utils_1.sleep)(this.pushingFrequency * 1000);
|
|
27
27
|
for (;;) {
|
|
28
|
+
// We will push all prices whose update condition is YES or EARLY as long as there is
|
|
29
|
+
// at least one YES.
|
|
30
|
+
let pushThresholdMet = false;
|
|
28
31
|
const pricesToPush = [];
|
|
29
32
|
const pubTimesToPush = [];
|
|
30
33
|
for (const priceConfig of this.priceConfigs) {
|
|
31
34
|
const priceId = priceConfig.id;
|
|
32
35
|
const targetLatestPrice = this.targetPriceListener.getLatestPriceInfo(priceId);
|
|
33
36
|
const sourceLatestPrice = this.sourcePriceListener.getLatestPriceInfo(priceId);
|
|
34
|
-
|
|
37
|
+
const priceShouldUpdate = (0, price_config_1.shouldUpdate)(priceConfig, sourceLatestPrice, targetLatestPrice);
|
|
38
|
+
if (priceShouldUpdate == price_config_1.UpdateCondition.YES) {
|
|
39
|
+
pushThresholdMet = true;
|
|
40
|
+
}
|
|
41
|
+
if (priceShouldUpdate == price_config_1.UpdateCondition.YES ||
|
|
42
|
+
priceShouldUpdate == price_config_1.UpdateCondition.EARLY) {
|
|
35
43
|
pricesToPush.push(priceConfig);
|
|
36
44
|
pubTimesToPush.push((targetLatestPrice?.publishTime || 0) + 1);
|
|
37
45
|
}
|
|
38
46
|
}
|
|
39
|
-
if (
|
|
47
|
+
if (pushThresholdMet) {
|
|
40
48
|
console.log("Some of the above values passed the threshold. Will push the price.");
|
|
41
49
|
// note that the priceIds are without leading "0x"
|
|
42
50
|
const priceIds = pricesToPush.map((priceConfig) => priceConfig.id);
|
package/lib/evm/command.d.ts
CHANGED
|
@@ -0,0 +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"}
|
package/lib/evm/command.js
CHANGED
|
@@ -88,6 +88,9 @@ exports.default = {
|
|
|
88
88
|
const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
|
|
89
89
|
const pythListener = new pyth_price_listener_1.PythPriceListener(priceServiceConnection, priceItems);
|
|
90
90
|
const pythContractFactory = new evm_1.PythContractFactory(endpoint, mnemonic, pythContractAddress);
|
|
91
|
+
console.log(`Pushing updates from wallet address: ${pythContractFactory
|
|
92
|
+
.createWeb3PayerProvider()
|
|
93
|
+
.getAddress()}`);
|
|
91
94
|
const evmListener = new evm_1.EvmPriceListener(pythContractFactory, priceItems, {
|
|
92
95
|
pollingFrequency,
|
|
93
96
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-gas-station.d.ts","sourceRoot":"","sources":["../../src/evm/custom-gas-station.ts"],"names":[],"mappings":"AAWA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,YAAY,CAElB;gBACU,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAKlC,iBAAiB;YAIT,yBAAyB;CAexC;AAED,wBAAgB,mBAAmB,CACjC,gBAAgB,CAAC,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,MAAM,gCAKjB"}
|
package/lib/evm/evm.d.ts
CHANGED
|
@@ -0,0 +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"}
|
package/lib/evm/evm.js
CHANGED
|
@@ -151,7 +151,7 @@ class EvmPricePusher {
|
|
|
151
151
|
// doesn't return any information why the call has reverted. Assuming that
|
|
152
152
|
// the update data is valid there is no possible rejection cause other than
|
|
153
153
|
// the target chain price being already updated.
|
|
154
|
-
console.log("Execution reverted. With high
|
|
154
|
+
console.log("Execution reverted. With high probability, the target chain price " +
|
|
155
155
|
"has already updated, Skipping this push.");
|
|
156
156
|
return;
|
|
157
157
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/injective/command.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;oBA2BL,GAAG;;AAxB9B,wBA4FE"}
|
|
@@ -20,6 +20,7 @@ export declare class InjectivePricePusher implements IPricePusher {
|
|
|
20
20
|
private grpcEndpoint;
|
|
21
21
|
private wallet;
|
|
22
22
|
private chainConfig;
|
|
23
|
+
private account;
|
|
23
24
|
constructor(priceServiceConnection: PriceServiceConnection, pythContractAddress: string, grpcEndpoint: string, mnemonic: string, chainConfig?: Partial<InjectiveConfig>);
|
|
24
25
|
private injectiveAddress;
|
|
25
26
|
private signAndBroadcastMsg;
|
|
@@ -27,3 +28,4 @@ export declare class InjectivePricePusher implements IPricePusher {
|
|
|
27
28
|
updatePriceFeed(priceIds: string[], pubTimesToPush: number[]): Promise<void>;
|
|
28
29
|
}
|
|
29
30
|
export {};
|
|
31
|
+
//# sourceMappingURL=injective.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injective.d.ts","sourceRoot":"","sources":["../../src/injective/injective.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,sBAAsB,EACvB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAiC7C,qBAAa,sBAAuB,SAAQ,kBAAkB;IAE1D,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,YAAY;gBADZ,mBAAmB,EAAE,MAAM,EAC3B,YAAY,EAAE,MAAM,EAC5B,UAAU,EAAE,SAAS,EAAE,EACvB,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAKG,mBAAmB,CACvB,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CA6BlC;AAED,KAAK,eAAe,GAAG;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AACF,qBAAa,oBAAqB,YAAW,YAAY;IAMrD,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,YAAY;IAPtB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,OAAO,CAAwB;gBAG7B,sBAAsB,EAAE,sBAAsB,EAC9C,mBAAmB,EAAE,MAAM,EAC3B,YAAY,EAAE,MAAM,EAC5B,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC;IAWxC,OAAO,CAAC,gBAAgB;YAIV,mBAAmB;IAkE3B,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAU1D,eAAe,CACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAAC,IAAI,CAAC;CAoDjB"}
|
|
@@ -41,6 +41,7 @@ class InjectivePricePusher {
|
|
|
41
41
|
grpcEndpoint;
|
|
42
42
|
wallet;
|
|
43
43
|
chainConfig;
|
|
44
|
+
account = null;
|
|
44
45
|
constructor(priceServiceConnection, pythContractAddress, grpcEndpoint, mnemonic, chainConfig) {
|
|
45
46
|
this.priceServiceConnection = priceServiceConnection;
|
|
46
47
|
this.pythContractAddress = pythContractAddress;
|
|
@@ -57,10 +58,11 @@ class InjectivePricePusher {
|
|
|
57
58
|
}
|
|
58
59
|
async signAndBroadcastMsg(msg) {
|
|
59
60
|
const chainGrpcAuthApi = new sdk_ts_1.ChainGrpcAuthApi(this.grpcEndpoint);
|
|
60
|
-
|
|
61
|
+
// Fetch the latest account details only if it's not stored.
|
|
62
|
+
this.account ??= await chainGrpcAuthApi.fetchAccount(this.injectiveAddress());
|
|
61
63
|
const { txRaw: simulateTxRaw } = (0, sdk_ts_1.createTransactionFromMsg)({
|
|
62
|
-
sequence: account.baseAccount.sequence,
|
|
63
|
-
accountNumber: account.baseAccount.accountNumber,
|
|
64
|
+
sequence: this.account.baseAccount.sequence,
|
|
65
|
+
accountNumber: this.account.baseAccount.accountNumber,
|
|
64
66
|
message: msg,
|
|
65
67
|
chainId: this.chainConfig.chainId,
|
|
66
68
|
pubKey: this.wallet.toPublicKey().toBase64(),
|
|
@@ -72,30 +74,41 @@ class InjectivePricePusher {
|
|
|
72
74
|
// gas passed with the transaction should be more than that
|
|
73
75
|
// in order for it to be successfully executed
|
|
74
76
|
// this multiplier takes care of that
|
|
77
|
+
const gas = (gasUsed * this.chainConfig.gasMultiplier).toFixed();
|
|
75
78
|
const fee = {
|
|
76
79
|
amount: [
|
|
77
80
|
{
|
|
78
81
|
denom: "inj",
|
|
79
|
-
amount: (
|
|
80
|
-
this.chainConfig.gasPrice *
|
|
81
|
-
this.chainConfig.gasMultiplier).toFixed(),
|
|
82
|
+
amount: (Number(gas) * this.chainConfig.gasPrice).toFixed(),
|
|
82
83
|
},
|
|
83
84
|
],
|
|
84
|
-
gas
|
|
85
|
+
gas,
|
|
85
86
|
};
|
|
86
87
|
const { signBytes, txRaw } = (0, sdk_ts_1.createTransactionFromMsg)({
|
|
87
|
-
sequence: account.baseAccount.sequence,
|
|
88
|
-
accountNumber: account.baseAccount.accountNumber,
|
|
88
|
+
sequence: this.account.baseAccount.sequence,
|
|
89
|
+
accountNumber: this.account.baseAccount.accountNumber,
|
|
89
90
|
message: msg,
|
|
90
91
|
chainId: this.chainConfig.chainId,
|
|
91
92
|
fee,
|
|
92
93
|
pubKey: this.wallet.toPublicKey().toBase64(),
|
|
93
94
|
});
|
|
94
95
|
const sig = await this.wallet.sign(Buffer.from(signBytes));
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
try {
|
|
97
|
+
this.account.baseAccount.sequence++;
|
|
98
|
+
/** Append Signatures */
|
|
99
|
+
txRaw.signatures = [sig];
|
|
100
|
+
// this takes approx 5 seconds
|
|
101
|
+
const txResponse = await txService.broadcast(txRaw);
|
|
102
|
+
return txResponse;
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
// The sequence number was invalid and hence we will have to fetch it again.
|
|
106
|
+
if (JSON.stringify(e).match(/account sequence mismatch/) !== null) {
|
|
107
|
+
// We need to fetch the account details again.
|
|
108
|
+
this.account = null;
|
|
109
|
+
}
|
|
110
|
+
throw e;
|
|
111
|
+
}
|
|
99
112
|
}
|
|
100
113
|
async getPriceFeedUpdateObject(priceIds) {
|
|
101
114
|
const vaas = await this.priceServiceConnection.getLatestVaas(priceIds);
|
|
@@ -121,22 +134,12 @@ class InjectivePricePusher {
|
|
|
121
134
|
console.error(e);
|
|
122
135
|
return;
|
|
123
136
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
},
|
|
131
|
-
})).toString("base64"));
|
|
132
|
-
const json = Buffer.from(data).toString();
|
|
133
|
-
updateFeeQueryResponse = JSON.parse(json);
|
|
134
|
-
}
|
|
135
|
-
catch (e) {
|
|
136
|
-
console.error("Error fetching update fee");
|
|
137
|
-
console.error(e);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
137
|
+
// In order to reduce the number of API calls
|
|
138
|
+
// We are calculating the fee using the same logic as in contract.
|
|
139
|
+
const updateFeeQueryResponse = {
|
|
140
|
+
denom: "inj",
|
|
141
|
+
amount: priceFeedUpdateObject.update_price_feeds.data.length.toFixed(),
|
|
142
|
+
};
|
|
140
143
|
try {
|
|
141
144
|
const executeMsg = sdk_ts_1.MsgExecuteContract.fromJSON({
|
|
142
145
|
sender: this.injectiveAddress(),
|
|
@@ -145,8 +148,6 @@ class InjectivePricePusher {
|
|
|
145
148
|
funds: [updateFeeQueryResponse],
|
|
146
149
|
});
|
|
147
150
|
const rs = await this.signAndBroadcastMsg(executeMsg);
|
|
148
|
-
if (rs.code !== 0)
|
|
149
|
-
throw new Error("Error: transaction failed");
|
|
150
151
|
console.log("Succesfully broadcasted txHash:", rs.txHash);
|
|
151
152
|
}
|
|
152
153
|
catch (e) {
|
package/lib/interface.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../src/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,SAAS,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,aAAa,CAAC;CAC5B,CAAC;AAEF,MAAM,WAAW,cAAc;IAE7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;CAC5D;AAED,8BAAsB,kBAAmB,YAAW,cAAc;IAK9D,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,gBAAgB;IACxB,SAAS,CAAC,UAAU,EAAE,SAAS,EAAE;IANnC,OAAO,CAAC,eAAe,CAA4B;IACnD,SAAS,CAAC,cAAc,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;gBAGvC,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,iBAAiB,EACjC,UAAU,EAAE,SAAS,EAAE;IAQ7B,KAAK;YASG,UAAU;IASxB,SAAS,CAAC,qBAAqB,CAC7B,OAAO,EAAE,SAAS,EAClB,aAAa,EAAE,SAAS;IAkB1B,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI1D,QAAQ,CAAC,mBAAmB,CAC1B,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,eAAe,CACb,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,aAAa,EAAE,GAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB"}
|
package/lib/options.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,eAAO,MAAM,oBAAoB;;CAOhC,CAAC;AAEF,eAAO,MAAM,mBAAmB;;CAQ/B,CAAC;AAEF,eAAO,MAAM,eAAe;;CAM3B,CAAC;AAEF,eAAO,MAAM,gBAAgB;;CAQ5B,CAAC;AAEF,eAAO,MAAM,gBAAgB;;CAU5B,CAAC;AAEF,eAAO,MAAM,YAAY;;CAMxB,CAAC"}
|
package/lib/price-config.d.ts
CHANGED
|
@@ -7,12 +7,21 @@ export type PriceConfig = {
|
|
|
7
7
|
timeDifference: DurationInSeconds;
|
|
8
8
|
priceDeviation: PctNumber;
|
|
9
9
|
confidenceRatio: PctNumber;
|
|
10
|
+
earlyUpdateTimeDifference: DurationInSeconds | undefined;
|
|
11
|
+
earlyUpdatePriceDeviation: PctNumber | undefined;
|
|
12
|
+
earlyUpdateConfidenceRatio: PctNumber | undefined;
|
|
10
13
|
};
|
|
11
14
|
export declare function readPriceConfigFile(path: string): PriceConfig[];
|
|
15
|
+
export declare enum UpdateCondition {
|
|
16
|
+
YES = 0,
|
|
17
|
+
EARLY = 1,
|
|
18
|
+
NO = 2
|
|
19
|
+
}
|
|
12
20
|
/**
|
|
13
21
|
* Checks whether on-chain price needs to be updated with the latest pyth price information.
|
|
14
22
|
*
|
|
15
23
|
* @param priceConfig Config of the price feed to check
|
|
16
24
|
* @returns True if the on-chain price needs to be updated.
|
|
17
25
|
*/
|
|
18
|
-
export declare function shouldUpdate(priceConfig: PriceConfig, sourceLatestPrice: PriceInfo | undefined, targetLatestPrice: PriceInfo | undefined):
|
|
26
|
+
export declare function shouldUpdate(priceConfig: PriceConfig, sourceLatestPrice: PriceInfo | undefined, targetLatestPrice: PriceInfo | undefined): UpdateCondition;
|
|
27
|
+
//# sourceMappingURL=price-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price-config.d.ts","sourceRoot":"","sources":["../src/price-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAI9D,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAmB,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAuBxC,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,SAAS,CAAC;IACd,cAAc,EAAE,iBAAiB,CAAC;IAClC,cAAc,EAAE,SAAS,CAAC;IAC1B,eAAe,EAAE,SAAS,CAAC;IAI3B,yBAAyB,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACzD,yBAAyB,EAAE,SAAS,GAAG,SAAS,CAAC;IACjD,0BAA0B,EAAE,SAAS,GAAG,SAAS,CAAC;CACnD,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAsB/D;AAED,oBAAY,eAAe;IAEzB,GAAG,IAAA;IAEH,KAAK,IAAA;IAEL,EAAE,IAAA;CACH;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,WAAW,EACxB,iBAAiB,EAAE,SAAS,GAAG,SAAS,EACxC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GACvC,eAAe,CAmEjB"}
|
package/lib/price-config.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.shouldUpdate = exports.readPriceConfigFile = void 0;
|
|
6
|
+
exports.shouldUpdate = exports.UpdateCondition = exports.readPriceConfigFile = void 0;
|
|
7
7
|
const joi_1 = __importDefault(require("joi"));
|
|
8
8
|
const yaml_1 = __importDefault(require("yaml"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -17,6 +17,11 @@ const PriceConfigFileSchema = joi_1.default.array()
|
|
|
17
17
|
time_difference: joi_1.default.number().required(),
|
|
18
18
|
price_deviation: joi_1.default.number().required(),
|
|
19
19
|
confidence_ratio: joi_1.default.number().required(),
|
|
20
|
+
early_update: joi_1.default.object({
|
|
21
|
+
time_difference: joi_1.default.number().optional(),
|
|
22
|
+
price_deviation: joi_1.default.number().optional(),
|
|
23
|
+
confidence_ratio: joi_1.default.number().optional(),
|
|
24
|
+
}).optional(),
|
|
20
25
|
}))
|
|
21
26
|
.unique("id")
|
|
22
27
|
.unique("alias")
|
|
@@ -34,11 +39,23 @@ function readPriceConfigFile(path) {
|
|
|
34
39
|
timeDifference: priceConfigRaw.time_difference,
|
|
35
40
|
priceDeviation: priceConfigRaw.price_deviation,
|
|
36
41
|
confidenceRatio: priceConfigRaw.confidence_ratio,
|
|
42
|
+
earlyUpdateTimeDifference: priceConfigRaw.early_update?.time_difference,
|
|
43
|
+
earlyUpdatePriceDeviation: priceConfigRaw.early_update?.price_deviation,
|
|
44
|
+
earlyUpdateConfidenceRatio: priceConfigRaw.early_update?.confidence_ratio,
|
|
37
45
|
};
|
|
38
46
|
return priceConfig;
|
|
39
47
|
});
|
|
40
48
|
}
|
|
41
49
|
exports.readPriceConfigFile = readPriceConfigFile;
|
|
50
|
+
var UpdateCondition;
|
|
51
|
+
(function (UpdateCondition) {
|
|
52
|
+
// This price feed must be updated
|
|
53
|
+
UpdateCondition[UpdateCondition["YES"] = 0] = "YES";
|
|
54
|
+
// This price feed may be updated as part of a larger batch
|
|
55
|
+
UpdateCondition[UpdateCondition["EARLY"] = 1] = "EARLY";
|
|
56
|
+
// This price feed shouldn't be updated
|
|
57
|
+
UpdateCondition[UpdateCondition["NO"] = 2] = "NO";
|
|
58
|
+
})(UpdateCondition = exports.UpdateCondition || (exports.UpdateCondition = {}));
|
|
42
59
|
/**
|
|
43
60
|
* Checks whether on-chain price needs to be updated with the latest pyth price information.
|
|
44
61
|
*
|
|
@@ -49,16 +66,16 @@ function shouldUpdate(priceConfig, sourceLatestPrice, targetLatestPrice) {
|
|
|
49
66
|
const priceId = priceConfig.id;
|
|
50
67
|
// There is no price to update the target with.
|
|
51
68
|
if (sourceLatestPrice === undefined) {
|
|
52
|
-
return
|
|
69
|
+
return UpdateCondition.YES;
|
|
53
70
|
}
|
|
54
71
|
// It means that price never existed there. So we should push the latest price feed.
|
|
55
72
|
if (targetLatestPrice === undefined) {
|
|
56
73
|
console.log(`${priceConfig.alias} (${priceId}) is not available on the target network. Pushing the price.`);
|
|
57
|
-
return
|
|
74
|
+
return UpdateCondition.YES;
|
|
58
75
|
}
|
|
59
76
|
// The current price is not newer than the price onchain
|
|
60
77
|
if (sourceLatestPrice.publishTime < targetLatestPrice.publishTime) {
|
|
61
|
-
return
|
|
78
|
+
return UpdateCondition.NO;
|
|
62
79
|
}
|
|
63
80
|
const timeDifference = sourceLatestPrice.publishTime - targetLatestPrice.publishTime;
|
|
64
81
|
const priceDeviationPct = (Math.abs(Number(sourceLatestPrice.price) - Number(targetLatestPrice.price)) /
|
|
@@ -68,12 +85,24 @@ function shouldUpdate(priceConfig, sourceLatestPrice, targetLatestPrice) {
|
|
|
68
85
|
console.log(`Analyzing price ${priceConfig.alias} (${priceId})`);
|
|
69
86
|
console.log("Source latest price: ", sourceLatestPrice);
|
|
70
87
|
console.log("Target latest price: ", targetLatestPrice);
|
|
71
|
-
console.log(`Time difference: ${timeDifference} (< ${priceConfig.timeDifference}?) OR ` +
|
|
72
|
-
`Price deviation: ${priceDeviationPct.toFixed(5)}% (< ${priceConfig.priceDeviation}%?) OR ` +
|
|
73
|
-
`Confidence ratio: ${confidenceRatioPct.toFixed(5)}% (< ${priceConfig.confidenceRatio}%?)`);
|
|
74
|
-
|
|
88
|
+
console.log(`Time difference: ${timeDifference} (< ${priceConfig.timeDifference}? / early: < ${priceConfig.earlyUpdateTimeDifference}) OR ` +
|
|
89
|
+
`Price deviation: ${priceDeviationPct.toFixed(5)}% (< ${priceConfig.priceDeviation}%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?) OR ` +
|
|
90
|
+
`Confidence ratio: ${confidenceRatioPct.toFixed(5)}% (< ${priceConfig.confidenceRatio}%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?)`);
|
|
91
|
+
if (timeDifference >= priceConfig.timeDifference ||
|
|
75
92
|
priceDeviationPct >= priceConfig.priceDeviation ||
|
|
76
|
-
confidenceRatioPct >= priceConfig.confidenceRatio
|
|
77
|
-
|
|
93
|
+
confidenceRatioPct >= priceConfig.confidenceRatio) {
|
|
94
|
+
return UpdateCondition.YES;
|
|
95
|
+
}
|
|
96
|
+
else if ((priceConfig.earlyUpdateTimeDifference !== undefined &&
|
|
97
|
+
timeDifference >= priceConfig.earlyUpdateTimeDifference) ||
|
|
98
|
+
(priceConfig.earlyUpdatePriceDeviation !== undefined &&
|
|
99
|
+
priceDeviationPct >= priceConfig.earlyUpdatePriceDeviation) ||
|
|
100
|
+
(priceConfig.earlyUpdateConfidenceRatio !== undefined &&
|
|
101
|
+
confidenceRatioPct >= priceConfig.earlyUpdateConfidenceRatio)) {
|
|
102
|
+
return UpdateCondition.EARLY;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
return UpdateCondition.NO;
|
|
106
|
+
}
|
|
78
107
|
}
|
|
79
108
|
exports.shouldUpdate = shouldUpdate;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pyth-price-listener.d.ts","sourceRoot":"","sources":["../src/pyth-price-listener.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,sBAAsB,EACvB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnE,qBAAa,iBAAkB,YAAW,cAAc;IACtD,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,eAAe,CAA4B;gBAEvC,UAAU,EAAE,sBAAsB,EAAE,UAAU,EAAE,SAAS,EAAE;IAWjE,KAAK;IAmBX,OAAO,CAAC,cAAc;IAsBtB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;CAG3D"}
|
package/lib/sui/command.d.ts
CHANGED
|
@@ -14,7 +14,11 @@ declare const _default: {
|
|
|
14
14
|
"wormhole-package-id": Options;
|
|
15
15
|
"wormhole-state-id": Options;
|
|
16
16
|
"price-feed-to-price-info-object-table-id": Options;
|
|
17
|
+
"max-vaas-per-ptb": Options;
|
|
18
|
+
"num-gas-objects": Options;
|
|
19
|
+
"gas-budget": Options;
|
|
17
20
|
};
|
|
18
|
-
handler: (argv: any) => void
|
|
21
|
+
handler: (argv: any) => Promise<void>;
|
|
19
22
|
};
|
|
20
23
|
export default _default;
|
|
24
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +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"}
|
package/lib/sui/command.js
CHANGED
|
@@ -33,6 +33,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
33
33
|
const pyth_price_listener_1 = require("../pyth-price-listener");
|
|
34
34
|
const controller_1 = require("../controller");
|
|
35
35
|
const sui_1 = require("./sui");
|
|
36
|
+
const sui_js_1 = require("@mysten/sui.js");
|
|
36
37
|
exports.default = {
|
|
37
38
|
command: "sui",
|
|
38
39
|
describe: "Run price pusher for sui. Most of the arguments below are" +
|
|
@@ -77,14 +78,32 @@ exports.default = {
|
|
|
77
78
|
type: "string",
|
|
78
79
|
required: true,
|
|
79
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
|
+
"num-gas-objects": {
|
|
88
|
+
description: "Number of gas objects in the pool.",
|
|
89
|
+
type: "number",
|
|
90
|
+
required: true,
|
|
91
|
+
default: 30,
|
|
92
|
+
},
|
|
93
|
+
"gas-budget": {
|
|
94
|
+
description: "Gas budget for each price update",
|
|
95
|
+
type: "number",
|
|
96
|
+
required: true,
|
|
97
|
+
default: 500_000_000,
|
|
98
|
+
},
|
|
80
99
|
...options.priceConfigFile,
|
|
81
100
|
...options.priceServiceEndpoint,
|
|
82
101
|
...options.mnemonicFile,
|
|
83
102
|
...options.pollingFrequency,
|
|
84
103
|
...options.pushingFrequency,
|
|
85
104
|
},
|
|
86
|
-
handler: function (argv) {
|
|
87
|
-
const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pushingFrequency, pollingFrequency, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, } = argv;
|
|
105
|
+
handler: async function (argv) {
|
|
106
|
+
const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pushingFrequency, pollingFrequency, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, numGasObjects, gasBudget, } = argv;
|
|
88
107
|
const priceConfigs = (0, price_config_1.readPriceConfigFile)(priceConfigFile);
|
|
89
108
|
const priceServiceConnection = new price_service_client_1.PriceServiceConnection(priceServiceEndpoint, {
|
|
90
109
|
logger: {
|
|
@@ -95,12 +114,18 @@ exports.default = {
|
|
|
95
114
|
debug: () => undefined,
|
|
96
115
|
trace: () => undefined,
|
|
97
116
|
},
|
|
117
|
+
priceFeedRequestConfig: {
|
|
118
|
+
binary: true,
|
|
119
|
+
},
|
|
98
120
|
});
|
|
99
121
|
const mnemonic = fs_1.default.readFileSync(mnemonicFile, "utf-8").trim();
|
|
122
|
+
console.log(`Pushing updates from wallet address: ${sui_js_1.Ed25519Keypair.deriveKeypair(mnemonic)
|
|
123
|
+
.getPublicKey()
|
|
124
|
+
.toSuiAddress()}`);
|
|
100
125
|
const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
|
|
101
126
|
const pythListener = new pyth_price_listener_1.PythPriceListener(priceServiceConnection, priceItems);
|
|
102
127
|
const suiListener = new sui_1.SuiPriceListener(pythPackageId, priceFeedToPriceInfoObjectTableId, endpoint, priceItems, { pollingFrequency });
|
|
103
|
-
const suiPusher =
|
|
128
|
+
const suiPusher = await sui_1.SuiPricePusher.createWithAutomaticGasPool(priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, numGasObjects);
|
|
104
129
|
const controller = new controller_1.Controller(priceConfigs, pythListener, suiListener, suiPusher, { pushingFrequency });
|
|
105
130
|
controller.start();
|
|
106
131
|
},
|
package/lib/sui/sui.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
5
|
export declare class SuiPriceListener extends ChainPriceListener {
|
|
5
6
|
private pythPackageId;
|
|
6
7
|
private priceFeedToPriceInfoObjectTableId;
|
|
@@ -11,13 +12,31 @@ export declare class SuiPriceListener extends ChainPriceListener {
|
|
|
11
12
|
getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined>;
|
|
12
13
|
}
|
|
13
14
|
export declare class SuiPricePusher implements IPricePusher {
|
|
15
|
+
private readonly signer;
|
|
14
16
|
private priceServiceConnection;
|
|
15
17
|
private pythPackageId;
|
|
16
18
|
private pythStateId;
|
|
17
19
|
private wormholePackageId;
|
|
18
20
|
private wormholeStateId;
|
|
19
21
|
private priceFeedToPriceInfoObjectTableId;
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
+
private maxVaasPerPtb;
|
|
23
|
+
private gasBudget;
|
|
24
|
+
private gasPool;
|
|
25
|
+
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[]);
|
|
26
|
+
/**
|
|
27
|
+
* Create a price pusher with a pool of `numGasObjects` gas coins that will be used to send transactions.
|
|
28
|
+
* The gas coins of the wallet for the provided mnemonic will be merged and then evenly split into `numGasObjects`.
|
|
29
|
+
*/
|
|
30
|
+
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>;
|
|
22
31
|
updatePriceFeed(priceIds: string[], pubTimesToPush: number[]): Promise<void>;
|
|
32
|
+
private createPriceUpdateTransaction;
|
|
33
|
+
/** Send every transaction in txs in parallel, returning when all transactions have completed. */
|
|
34
|
+
private sendTransactionBlocks;
|
|
35
|
+
/** Send a single transaction block using a gas coin from the pool. */
|
|
36
|
+
private sendTransactionBlock;
|
|
37
|
+
private static initializeGasPool;
|
|
38
|
+
private static getAllGasCoins;
|
|
39
|
+
private static splitGasCoinEqually;
|
|
40
|
+
private static mergeGasCoinsIntoOne;
|
|
23
41
|
}
|
|
42
|
+
//# sourceMappingURL=sui.d.ts.map
|
|
@@ -0,0 +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,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;mBA6Cb,iBAAiB;mBA2BjB,cAAc;mBAiCd,mBAAmB;mBAsCnB,oBAAoB;CA2C1C"}
|
package/lib/sui/sui.js
CHANGED
|
@@ -3,6 +3,10 @@ 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 GAS_FEE_FOR_SPLIT = 2_000_000_000;
|
|
7
|
+
// TODO: read this from on chain config
|
|
8
|
+
const MAX_NUM_GAS_OBJECTS_IN_PTB = 256;
|
|
9
|
+
const MAX_NUM_OBJECTS_IN_ARGUMENT = 510;
|
|
6
10
|
class SuiPriceListener extends interface_1.ChainPriceListener {
|
|
7
11
|
pythPackageId;
|
|
8
12
|
priceFeedToPriceInfoObjectTableId;
|
|
@@ -48,21 +52,39 @@ class SuiPriceListener extends interface_1.ChainPriceListener {
|
|
|
48
52
|
}
|
|
49
53
|
exports.SuiPriceListener = SuiPriceListener;
|
|
50
54
|
class SuiPricePusher {
|
|
55
|
+
signer;
|
|
51
56
|
priceServiceConnection;
|
|
52
57
|
pythPackageId;
|
|
53
58
|
pythStateId;
|
|
54
59
|
wormholePackageId;
|
|
55
60
|
wormholeStateId;
|
|
56
61
|
priceFeedToPriceInfoObjectTableId;
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
maxVaasPerPtb;
|
|
63
|
+
gasBudget;
|
|
64
|
+
gasPool;
|
|
65
|
+
constructor(signer, priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, gasPool) {
|
|
66
|
+
this.signer = signer;
|
|
59
67
|
this.priceServiceConnection = priceServiceConnection;
|
|
60
68
|
this.pythPackageId = pythPackageId;
|
|
61
69
|
this.pythStateId = pythStateId;
|
|
62
70
|
this.wormholePackageId = wormholePackageId;
|
|
63
71
|
this.wormholeStateId = wormholeStateId;
|
|
64
72
|
this.priceFeedToPriceInfoObjectTableId = priceFeedToPriceInfoObjectTableId;
|
|
65
|
-
this.
|
|
73
|
+
this.maxVaasPerPtb = maxVaasPerPtb;
|
|
74
|
+
this.gasBudget = gasBudget;
|
|
75
|
+
this.gasPool = gasPool;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create a price pusher with a pool of `numGasObjects` gas coins that will be used to send transactions.
|
|
79
|
+
* The gas coins of the wallet for the provided mnemonic will be merged and then evenly split into `numGasObjects`.
|
|
80
|
+
*/
|
|
81
|
+
static async createWithAutomaticGasPool(priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, numGasObjects) {
|
|
82
|
+
if (numGasObjects > MAX_NUM_OBJECTS_IN_ARGUMENT) {
|
|
83
|
+
throw new Error(`numGasObjects cannot be greater than ${MAX_NUM_OBJECTS_IN_ARGUMENT} until we implement split chunking`);
|
|
84
|
+
}
|
|
85
|
+
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 })));
|
|
86
|
+
const gasPool = await SuiPricePusher.initializeGasPool(signer, numGasObjects);
|
|
87
|
+
return new SuiPricePusher(signer, priceServiceConnection, pythPackageId, pythStateId, wormholePackageId, wormholeStateId, priceFeedToPriceInfoObjectTableId, maxVaasPerPtb, endpoint, mnemonic, gasBudget, gasPool);
|
|
66
88
|
}
|
|
67
89
|
async updatePriceFeed(priceIds, pubTimesToPush) {
|
|
68
90
|
if (priceIds.length === 0) {
|
|
@@ -70,8 +92,43 @@ class SuiPricePusher {
|
|
|
70
92
|
}
|
|
71
93
|
if (priceIds.length !== pubTimesToPush.length)
|
|
72
94
|
throw new Error("Invalid arguments");
|
|
95
|
+
if (this.gasPool.length === 0) {
|
|
96
|
+
console.warn("Skipping update: no available gas coin.");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const priceFeeds = await this.priceServiceConnection.getLatestPriceFeeds(priceIds);
|
|
100
|
+
if (priceFeeds === undefined) {
|
|
101
|
+
console.warn("Failed to fetch price updates. Skipping push.");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const vaaToPriceFeedIds = new Map();
|
|
105
|
+
for (const priceFeed of priceFeeds) {
|
|
106
|
+
// The ! will succeed as long as the priceServiceConnection is configured to return binary vaa data (which it is).
|
|
107
|
+
const vaa = priceFeed.getVAA();
|
|
108
|
+
if (!vaaToPriceFeedIds.has(vaa)) {
|
|
109
|
+
vaaToPriceFeedIds.set(vaa, []);
|
|
110
|
+
}
|
|
111
|
+
vaaToPriceFeedIds.get(vaa).push(priceFeed.id);
|
|
112
|
+
}
|
|
113
|
+
const txs = [];
|
|
114
|
+
let currentBatchVaas = [];
|
|
115
|
+
let currentBatchPriceFeedIds = [];
|
|
116
|
+
for (const [vaa, priceFeedIds] of vaaToPriceFeedIds.entries()) {
|
|
117
|
+
currentBatchVaas.push(vaa);
|
|
118
|
+
currentBatchPriceFeedIds.push(...priceFeedIds);
|
|
119
|
+
if (currentBatchVaas.length >= this.maxVaasPerPtb) {
|
|
120
|
+
const tx = await this.createPriceUpdateTransaction(currentBatchVaas, currentBatchPriceFeedIds);
|
|
121
|
+
if (tx !== undefined) {
|
|
122
|
+
txs.push(tx);
|
|
123
|
+
}
|
|
124
|
+
currentBatchVaas = [];
|
|
125
|
+
currentBatchPriceFeedIds = [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
await this.sendTransactionBlocks(txs);
|
|
129
|
+
}
|
|
130
|
+
async createPriceUpdateTransaction(vaas, priceIds) {
|
|
73
131
|
const tx = new sui_js_1.TransactionBlock();
|
|
74
|
-
const vaas = await this.priceServiceConnection.getLatestVaas(priceIds);
|
|
75
132
|
// Parse our batch price attestation VAA bytes using Wormhole.
|
|
76
133
|
// Check out the Wormhole cross-chain bridge and generic messaging protocol here:
|
|
77
134
|
// https://github.com/wormhole-foundation/wormhole
|
|
@@ -110,7 +167,7 @@ class SuiPricePusher {
|
|
|
110
167
|
catch (e) {
|
|
111
168
|
console.log("Error fetching price info object id for ", priceId);
|
|
112
169
|
console.error(e);
|
|
113
|
-
return;
|
|
170
|
+
return undefined;
|
|
114
171
|
}
|
|
115
172
|
const coin = tx.splitCoins(tx.gas, [tx.pure(1)]);
|
|
116
173
|
[price_updates_hot_potato] = tx.moveCall({
|
|
@@ -131,29 +188,132 @@ class SuiPricePusher {
|
|
|
131
188
|
arguments: [price_updates_hot_potato],
|
|
132
189
|
typeArguments: [`${this.pythPackageId}::price_info::PriceInfo`],
|
|
133
190
|
});
|
|
191
|
+
return tx;
|
|
192
|
+
}
|
|
193
|
+
/** Send every transaction in txs in parallel, returning when all transactions have completed. */
|
|
194
|
+
async sendTransactionBlocks(txs) {
|
|
195
|
+
return Promise.all(txs.map((tx) => this.sendTransactionBlock(tx)));
|
|
196
|
+
}
|
|
197
|
+
/** Send a single transaction block using a gas coin from the pool. */
|
|
198
|
+
async sendTransactionBlock(tx) {
|
|
199
|
+
const gasObject = this.gasPool.shift();
|
|
200
|
+
if (gasObject === undefined) {
|
|
201
|
+
console.warn("No available gas coin. Skipping push.");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
let nextGasObject = undefined;
|
|
134
205
|
try {
|
|
206
|
+
tx.setGasPayment([gasObject]);
|
|
207
|
+
tx.setGasBudget(this.gasBudget);
|
|
135
208
|
const result = await this.signer.signAndExecuteTransactionBlock({
|
|
136
209
|
transactionBlock: tx,
|
|
137
210
|
options: {
|
|
138
|
-
showInput: true,
|
|
139
211
|
showEffects: true,
|
|
140
|
-
showEvents: true,
|
|
141
|
-
showObjectChanges: true,
|
|
142
|
-
showBalanceChanges: true,
|
|
143
212
|
},
|
|
144
213
|
});
|
|
214
|
+
nextGasObject = (0, sui_js_1.getTransactionEffects)(result)
|
|
215
|
+
?.mutated?.map((obj) => obj.reference)
|
|
216
|
+
.find((ref) => ref.objectId === gasObject.objectId);
|
|
145
217
|
console.log("Successfully updated price with transaction digest ", result.digest);
|
|
146
218
|
}
|
|
147
219
|
catch (e) {
|
|
148
220
|
console.log("Error when signAndExecuteTransactionBlock");
|
|
149
221
|
if (String(e).includes("GasBalanceTooLow")) {
|
|
150
|
-
console.
|
|
151
|
-
|
|
222
|
+
console.warn(`The balance of gas object ${gasObject.objectId} is too low. Removing from pool.`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
nextGasObject = gasObject;
|
|
152
226
|
}
|
|
153
227
|
console.error(e);
|
|
154
|
-
|
|
228
|
+
}
|
|
229
|
+
if (nextGasObject !== undefined) {
|
|
230
|
+
this.gasPool.push(nextGasObject);
|
|
155
231
|
}
|
|
156
232
|
}
|
|
233
|
+
// This function will smash all coins owned by the signer into one, and then
|
|
234
|
+
// split them equally into numGasObjects.
|
|
235
|
+
static async initializeGasPool(signer, numGasObjects) {
|
|
236
|
+
const signerAddress = await signer.getAddress();
|
|
237
|
+
const { totalBalance: balance } = await signer.provider.getBalance({
|
|
238
|
+
owner: signerAddress,
|
|
239
|
+
});
|
|
240
|
+
const splitAmount = (BigInt(balance) - BigInt(GAS_FEE_FOR_SPLIT)) / BigInt(numGasObjects);
|
|
241
|
+
const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne(signer, signerAddress);
|
|
242
|
+
const gasPool = await SuiPricePusher.splitGasCoinEqually(signer, signerAddress, Number(splitAmount), numGasObjects, consolidatedCoin);
|
|
243
|
+
console.log("Gas pool is filled with coins: ", gasPool);
|
|
244
|
+
return gasPool;
|
|
245
|
+
}
|
|
246
|
+
static async getAllGasCoins(provider, owner) {
|
|
247
|
+
let hasNextPage = true;
|
|
248
|
+
let cursor;
|
|
249
|
+
const coins = new Set([]);
|
|
250
|
+
let numCoins = 0;
|
|
251
|
+
while (hasNextPage) {
|
|
252
|
+
const paginatedCoins = await provider.getCoins({
|
|
253
|
+
owner,
|
|
254
|
+
cursor,
|
|
255
|
+
});
|
|
256
|
+
numCoins += paginatedCoins.data.length;
|
|
257
|
+
paginatedCoins.data.forEach((c) => coins.add(JSON.stringify({
|
|
258
|
+
objectId: c.coinObjectId,
|
|
259
|
+
version: c.version,
|
|
260
|
+
digest: c.digest,
|
|
261
|
+
})));
|
|
262
|
+
hasNextPage = paginatedCoins.hasNextPage;
|
|
263
|
+
cursor = paginatedCoins.nextCursor;
|
|
264
|
+
}
|
|
265
|
+
if (numCoins !== coins.size) {
|
|
266
|
+
throw new Error("Unexpected getCoins result: duplicate coins found");
|
|
267
|
+
}
|
|
268
|
+
return [...coins].map((item) => JSON.parse(item));
|
|
269
|
+
}
|
|
270
|
+
static async splitGasCoinEqually(signer, signerAddress, splitAmount, numGasObjects, gasCoin) {
|
|
271
|
+
// TODO: implement chunking if numGasObjects exceeds MAX_NUM_CREATED_OBJECTS
|
|
272
|
+
const tx = new sui_js_1.TransactionBlock();
|
|
273
|
+
const coins = tx.splitCoins(tx.gas, Array.from({ length: numGasObjects }, () => tx.pure(splitAmount)));
|
|
274
|
+
tx.transferObjects(Array.from({ length: numGasObjects }, (_, i) => coins[i]), tx.pure(signerAddress));
|
|
275
|
+
tx.setGasPayment([gasCoin]);
|
|
276
|
+
const result = await signer.signAndExecuteTransactionBlock({
|
|
277
|
+
transactionBlock: tx,
|
|
278
|
+
options: { showEffects: true },
|
|
279
|
+
});
|
|
280
|
+
const error = (0, sui_js_1.getExecutionStatusError)(result);
|
|
281
|
+
if (error) {
|
|
282
|
+
throw new Error(`Failed to initialize gas pool: ${error}. Try re-running the script`);
|
|
283
|
+
}
|
|
284
|
+
const newCoins = (0, sui_js_1.getCreatedObjects)(result).map((obj) => obj.reference);
|
|
285
|
+
if (newCoins.length !== numGasObjects) {
|
|
286
|
+
throw new Error(`Failed to initialize gas pool. Expected ${numGasObjects}, got: ${newCoins}`);
|
|
287
|
+
}
|
|
288
|
+
return newCoins;
|
|
289
|
+
}
|
|
290
|
+
static async mergeGasCoinsIntoOne(signer, owner) {
|
|
291
|
+
const gasCoins = await SuiPricePusher.getAllGasCoins(signer.provider, owner);
|
|
292
|
+
// skip merging if there is only one coin
|
|
293
|
+
if (gasCoins.length === 1) {
|
|
294
|
+
return gasCoins[0];
|
|
295
|
+
}
|
|
296
|
+
const gasCoinsChunks = chunkArray(gasCoins, MAX_NUM_GAS_OBJECTS_IN_PTB - 2);
|
|
297
|
+
let finalCoin;
|
|
298
|
+
for (let i = 0; i < gasCoinsChunks.length; i++) {
|
|
299
|
+
const mergeTx = new sui_js_1.TransactionBlock();
|
|
300
|
+
let coins = gasCoinsChunks[i];
|
|
301
|
+
if (finalCoin) {
|
|
302
|
+
coins = [finalCoin, ...coins];
|
|
303
|
+
}
|
|
304
|
+
mergeTx.setGasPayment(coins);
|
|
305
|
+
const mergeResult = await signer.signAndExecuteTransactionBlock({
|
|
306
|
+
transactionBlock: mergeTx,
|
|
307
|
+
options: { showEffects: true },
|
|
308
|
+
});
|
|
309
|
+
const error = (0, sui_js_1.getExecutionStatusError)(mergeResult);
|
|
310
|
+
if (error) {
|
|
311
|
+
throw new Error(`Failed to merge coins when initializing gas pool: ${error}. Try re-running the script`);
|
|
312
|
+
}
|
|
313
|
+
finalCoin = (0, sui_js_1.getTransactionEffects)(mergeResult).mutated.map((obj) => obj.reference)[0];
|
|
314
|
+
}
|
|
315
|
+
return finalCoin;
|
|
316
|
+
}
|
|
157
317
|
}
|
|
158
318
|
exports.SuiPricePusher = SuiPricePusher;
|
|
159
319
|
// We are calculating stored price info object id for given price id
|
|
@@ -187,3 +347,12 @@ async function priceIdToPriceInfoObjectId(provider, pythPackageId, priceFeedToPr
|
|
|
187
347
|
CACHE[priceId] = priceInfoObjectId;
|
|
188
348
|
return priceInfoObjectId;
|
|
189
349
|
}
|
|
350
|
+
function chunkArray(array, size) {
|
|
351
|
+
const chunked = [];
|
|
352
|
+
let index = 0;
|
|
353
|
+
while (index < array.length) {
|
|
354
|
+
chunked.push(array.slice(index, size + index));
|
|
355
|
+
index += size;
|
|
356
|
+
}
|
|
357
|
+
return chunked;
|
|
358
|
+
}
|
package/lib/utils.d.ts
CHANGED
|
@@ -10,3 +10,4 @@ export declare function removeLeading0x(id: HexString): HexString;
|
|
|
10
10
|
export declare function addLeading0x(id: HexString): HexString;
|
|
11
11
|
export declare function isWsEndpoint(endpoint: string): boolean;
|
|
12
12
|
export declare function verifyValidOption<options extends Readonly<Array<any>>, validOption extends options[number]>(option: any, validOptions: options): validOption;
|
|
13
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAE9D,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACvC,eAAO,MAAM,QAAQ,uCAAwC,CAAC;AAC9D,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9C,eAAO,MAAM,iBAAiB,gBAAiB,CAAC;AAChD,MAAM,MAAM,gBAAgB,GAAG,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAEhE,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErD;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAKxD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAKrD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAStD;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,SAAS,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EACpC,WAAW,SAAS,OAAO,CAAC,MAAM,CAAC,EACnC,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,eAOnC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pythnetwork/price-pusher",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.4",
|
|
4
4
|
"description": "Pyth Price Pusher",
|
|
5
5
|
"homepage": "https://pyth.network",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"yaml": "^2.1.1",
|
|
64
64
|
"yargs": "^17.5.1"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "2c334b6b9249914c1f88541cb55235388e404030"
|
|
67
67
|
}
|