1
- import { EvmPriceServiceConnection } from "@pythnetwork/pyth-evm-js" ;
2
- import { DurationInSeconds , sleep } from "./utils" ;
3
- import { PriceListener } from "./price-listener" ;
1
+ import {
2
+ EvmPriceServiceConnection ,
3
+ UnixTimestamp ,
4
+ } from "@pythnetwork/pyth-evm-js" ;
5
+ import { addLeading0x , DurationInSeconds , sleep } from "./utils" ;
6
+ import { PriceInfo , PriceListener } from "./price-listener" ;
4
7
import { Contract } from "web3-eth-contract" ;
5
8
import AbstractPythAbi from "@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json" ;
6
9
import Web3 from "web3" ;
7
10
import HDWalletProvider from "@truffle/hdwallet-provider" ;
8
11
import { PriceConfig } from "./price-config" ;
12
+ import { TransactionReceipt } from "ethereum-protocol" ;
9
13
10
14
export class Pusher {
11
15
private connection : EvmPriceServiceConnection ;
12
16
private pythContract : Contract ;
13
17
private targetPriceListener : PriceListener ;
14
- private srcPriceListener : PriceListener ;
18
+ private sourcePriceListener : PriceListener ;
15
19
private priceConfigs : PriceConfig [ ] ;
16
20
17
21
private cooldownDuration : DurationInSeconds ;
@@ -22,15 +26,15 @@ export class Pusher {
22
26
mnemonic : string ,
23
27
pythContractAddr : string ,
24
28
targetPriceListener : PriceListener ,
25
- srcPriceListener : PriceListener ,
29
+ sourcePriceListener : PriceListener ,
26
30
priceConfigs : PriceConfig [ ] ,
27
31
config : {
28
32
cooldownDuration : DurationInSeconds ;
29
33
}
30
34
) {
31
35
this . connection = connection ;
32
36
this . targetPriceListener = targetPriceListener ;
33
- this . srcPriceListener = srcPriceListener ;
37
+ this . sourcePriceListener = sourcePriceListener ;
34
38
this . priceConfigs = priceConfigs ;
35
39
36
40
this . cooldownDuration = config . cooldownDuration ;
@@ -55,21 +59,46 @@ export class Pusher {
55
59
56
60
async start ( ) {
57
61
for ( ; ; ) {
58
- const pricesToPush = this . priceConfigs . filter (
59
- this . shouldUpdate . bind ( this )
60
- ) ;
61
- this . pushUpdates ( pricesToPush ) ;
62
+ const pricesToPush : PriceConfig [ ] = [ ] ;
63
+ const pubTimesToPush : UnixTimestamp [ ] = [ ] ;
64
+
65
+ for ( const priceConfig of this . priceConfigs ) {
66
+ const priceId = priceConfig . id ;
67
+
68
+ const targetLatestPrice =
69
+ this . targetPriceListener . getLatestPriceInfo ( priceId ) ;
70
+ const sourceLatestPrice =
71
+ this . sourcePriceListener . getLatestPriceInfo ( priceId ) ;
72
+
73
+ if (
74
+ this . shouldUpdate ( priceConfig , sourceLatestPrice , targetLatestPrice )
75
+ ) {
76
+ pricesToPush . push ( priceConfig ) ;
77
+ pubTimesToPush . push ( ( targetLatestPrice ?. publishTime || 0 ) + 1 ) ;
78
+ }
79
+ }
80
+ this . pushUpdates ( pricesToPush , pubTimesToPush ) ;
62
81
await sleep ( this . cooldownDuration * 1000 ) ;
63
82
}
64
83
}
65
84
66
- async pushUpdates ( pricesToPush : PriceConfig [ ] ) {
85
+ // The pubTimes are passed here to use the values that triggered the push.
86
+ // This is an optimization to avoid getting a newer value (as an update comes)
87
+ // and will help multiple price pushers to have consistent behaviour.
88
+ async pushUpdates (
89
+ pricesToPush : PriceConfig [ ] ,
90
+ pubTimesToPush : UnixTimestamp [ ]
91
+ ) {
67
92
if ( pricesToPush . length === 0 ) {
68
93
return ;
69
94
}
70
95
96
+ const priceIds = pricesToPush . map ( ( priceConfig ) =>
97
+ addLeading0x ( priceConfig . id )
98
+ ) ;
99
+
71
100
const priceFeedUpdateData = await this . connection . getPriceFeedsUpdateData (
72
- pricesToPush . map ( ( priceConfig ) => priceConfig . id )
101
+ priceIds
73
102
) ;
74
103
75
104
console . log (
@@ -80,10 +109,44 @@ export class Pusher {
80
109
) ;
81
110
82
111
this . pythContract . methods
83
- . updatePriceFeeds ( priceFeedUpdateData )
112
+ . updatePriceFeedsIfNecessary (
113
+ priceFeedUpdateData ,
114
+ priceIds ,
115
+ pubTimesToPush
116
+ )
84
117
. send ( )
85
118
. on ( "transactionHash" , ( hash : string ) => {
86
- console . log ( `Tx hash: ${ hash } ` ) ;
119
+ console . log ( `Successful. Tx hash: ${ hash } ` ) ;
120
+ } )
121
+ . on ( "error" , ( err : Error , receipt : TransactionReceipt ) => {
122
+ if (
123
+ err . message . includes (
124
+ "no prices in the submitted batch have fresh prices, so this update will have no effect"
125
+ )
126
+ ) {
127
+ console . log (
128
+ "The target chain price has already updated, Skipping this push."
129
+ ) ;
130
+ return ;
131
+ }
132
+
133
+ if ( err . message . includes ( "the tx doesn't have the correct nonce." ) ) {
134
+ console . log (
135
+ "Multiple users are using the same accounts and nonce is incorrect. Skipping this push."
136
+ ) ;
137
+ return ;
138
+ }
139
+
140
+ if (
141
+ err . message . includes ( "sender doesn't have enough funds to send tx." )
142
+ ) {
143
+ console . error ( "Payer is out of balance, please top it up." ) ;
144
+ throw err ;
145
+ }
146
+
147
+ console . error ( "An unidentified error has occured:" ) ;
148
+ console . error ( err , receipt ) ;
149
+ console . error ( "Skipping this push." ) ;
87
150
} ) ;
88
151
}
89
152
@@ -93,15 +156,15 @@ export class Pusher {
93
156
* @param priceConfig Config of the price feed to check
94
157
* @returns True if the on-chain price needs to be updated.
95
158
*/
96
- shouldUpdate ( priceConfig : PriceConfig ) : boolean {
159
+ shouldUpdate (
160
+ priceConfig : PriceConfig ,
161
+ sourceLatestPrice : PriceInfo | undefined ,
162
+ targetLatestPrice : PriceInfo | undefined
163
+ ) : boolean {
97
164
const priceId = priceConfig . id ;
98
165
99
- const targetLatestPrice =
100
- this . targetPriceListener . getLatestPriceInfo ( priceId ) ;
101
- const srcLatestPrice = this . srcPriceListener . getLatestPriceInfo ( priceId ) ;
102
-
103
166
// There is no price to update the target with.
104
- if ( srcLatestPrice === undefined ) {
167
+ if ( sourceLatestPrice === undefined ) {
105
168
return false ;
106
169
}
107
170
@@ -114,21 +177,21 @@ export class Pusher {
114
177
}
115
178
116
179
// The current price is not newer than the price onchain
117
- if ( srcLatestPrice . publishTime < targetLatestPrice . publishTime ) {
180
+ if ( sourceLatestPrice . publishTime < targetLatestPrice . publishTime ) {
118
181
return false ;
119
182
}
120
183
121
184
const timeDifference =
122
- srcLatestPrice . publishTime - targetLatestPrice . publishTime ;
185
+ sourceLatestPrice . publishTime - targetLatestPrice . publishTime ;
123
186
124
187
const priceDeviationPct =
125
188
( Math . abs (
126
- Number ( srcLatestPrice . price ) - Number ( targetLatestPrice . price )
189
+ Number ( sourceLatestPrice . price ) - Number ( targetLatestPrice . price )
127
190
) /
128
191
Number ( targetLatestPrice . price ) ) *
129
192
100 ;
130
193
const confidenceRatioPct = Math . abs (
131
- ( Number ( srcLatestPrice . conf ) / Number ( srcLatestPrice . price ) ) * 100
194
+ ( Number ( sourceLatestPrice . conf ) / Number ( sourceLatestPrice . price ) ) * 100
132
195
) ;
133
196
134
197
console . log ( `Analyzing price ${ priceConfig . alias } (${ priceId } )` ) ;
0 commit comments