1
1
use base64:: Engine ;
2
2
use futures_util:: StreamExt ;
3
- use pyth_lazer_client:: LazerClient ;
4
- use pyth_lazer_protocol:: message:: { EvmMessage , SolanaMessage } ;
3
+ use pyth_lazer_client:: { AnyResponse , LazerClient } ;
4
+ use pyth_lazer_protocol:: message:: {
5
+ EvmMessage , LeEcdsaMessage , LeUnsignedMessage , Message , SolanaMessage ,
6
+ } ;
5
7
use pyth_lazer_protocol:: payload:: PayloadData ;
6
8
use pyth_lazer_protocol:: router:: {
7
- Chain , Channel , DeliveryFormat , FixedRate , JsonBinaryEncoding , PriceFeedId , PriceFeedProperty ,
9
+ Channel , DeliveryFormat , FixedRate , Format , JsonBinaryEncoding , PriceFeedId , PriceFeedProperty ,
8
10
SubscriptionParams , SubscriptionParamsRepr ,
9
11
} ;
10
12
use pyth_lazer_protocol:: subscription:: { Request , Response , SubscribeRequest , SubscriptionId } ;
13
+ use tokio:: pin;
11
14
12
15
fn get_lazer_access_token ( ) -> String {
13
16
// Place your access token in your env at LAZER_ACCESS_TOKEN or set it here
@@ -22,7 +25,8 @@ async fn main() -> anyhow::Result<()> {
22
25
"wss://pyth-lazer.dourolabs.app/v1/stream" ,
23
26
& get_lazer_access_token ( ) ,
24
27
) ?;
25
- let mut stream = client. start ( ) . await ?;
28
+ let stream = client. start ( ) . await ?;
29
+ pin ! ( stream) ;
26
30
27
31
let subscription_requests = vec ! [
28
32
// Example subscription: Parsed JSON feed targeting Solana
@@ -36,7 +40,7 @@ async fn main() -> anyhow::Result<()> {
36
40
PriceFeedProperty :: BestAskPrice ,
37
41
PriceFeedProperty :: BestBidPrice ,
38
42
] ,
39
- chains : vec![ Chain :: Solana ] ,
43
+ formats : vec![ Format :: Solana ] ,
40
44
delivery_format: DeliveryFormat :: Json ,
41
45
json_binary_encoding: JsonBinaryEncoding :: Base64 ,
42
46
parsed: true ,
@@ -57,7 +61,7 @@ async fn main() -> anyhow::Result<()> {
57
61
PriceFeedProperty :: BestAskPrice ,
58
62
PriceFeedProperty :: BestBidPrice ,
59
63
] ,
60
- chains : vec![ Chain :: Evm , Chain :: Solana ] ,
64
+ formats : vec![ Format :: Evm , Format :: Solana ] ,
61
65
delivery_format: DeliveryFormat :: Binary ,
62
66
json_binary_encoding: JsonBinaryEncoding :: Base64 ,
63
67
parsed: false ,
@@ -80,49 +84,108 @@ async fn main() -> anyhow::Result<()> {
80
84
while let Some ( msg) = stream. next ( ) . await {
81
85
// The stream gives us base64-encoded binary messages. We need to decode, parse, and verify them.
82
86
match msg? {
83
- Response :: StreamUpdated ( update) => {
84
- if let Some ( evm_data) = update. payload . evm {
85
- // Decode binary data
86
- let binary_data =
87
- base64:: engine:: general_purpose:: STANDARD . decode ( & evm_data. data ) ?;
88
- let evm_message = EvmMessage :: deserialize_slice ( & binary_data) ?;
89
-
90
- // Parse and verify the EVM message
91
- let payload = parse_and_verify_evm_message ( & evm_message) ;
92
- println ! ( "EVM payload: {payload:?}\n " ) ;
93
- }
87
+ AnyResponse :: Json ( msg) => match msg {
88
+ Response :: StreamUpdated ( update) => {
89
+ println ! ( "Received a JSON update for {:?}" , update. subscription_id) ;
90
+ if let Some ( evm_data) = update. payload . evm {
91
+ // Decode binary data
92
+ let binary_data =
93
+ base64:: engine:: general_purpose:: STANDARD . decode ( & evm_data. data ) ?;
94
+ let evm_message = EvmMessage :: deserialize_slice ( & binary_data) ?;
95
+
96
+ // Parse and verify the EVM message
97
+ let payload = parse_and_verify_evm_message ( & evm_message) ;
98
+ println ! ( "EVM payload: {payload:?}" ) ;
99
+ }
94
100
95
- if let Some ( solana_data) = update. payload . solana {
96
- // Decode binary data
97
- let binary_data =
98
- base64:: engine:: general_purpose:: STANDARD . decode ( & solana_data. data ) ?;
99
- let solana_message = SolanaMessage :: deserialize_slice ( & binary_data) ?;
101
+ if let Some ( solana_data) = update. payload . solana {
102
+ // Decode binary data
103
+ let binary_data =
104
+ base64:: engine:: general_purpose:: STANDARD . decode ( & solana_data. data ) ?;
105
+ let solana_message = SolanaMessage :: deserialize_slice ( & binary_data) ?;
100
106
101
- // Parse and verify the Solana message
102
- let payload = parse_and_verify_solana_message ( & solana_message) ;
103
- println ! ( "Solana payload: {payload:?}\n " ) ;
104
- }
107
+ // Parse and verify the Solana message
108
+ let payload = parse_and_verify_solana_message ( & solana_message) ;
109
+ println ! ( "Solana payload: {payload:?}" ) ;
110
+ }
105
111
106
- if let Some ( parsed) = update. payload . parsed {
107
- // Parsed payloads (`parsed: true`) are already decoded and ready to use
108
- for feed in parsed. price_feeds {
109
- println ! (
110
- "Parsed payload: {:?}: {:?} at {:?}\n " ,
111
- feed. price_feed_id, feed, parsed. timestamp_us
112
- ) ;
112
+ if let Some ( data) = update. payload . le_ecdsa {
113
+ // Decode binary data
114
+ let binary_data =
115
+ base64:: engine:: general_purpose:: STANDARD . decode ( & data. data ) ?;
116
+ let message = LeEcdsaMessage :: deserialize_slice ( & binary_data) ?;
117
+
118
+ // Parse and verify the message
119
+ let payload = parse_and_verify_le_ecdsa_message ( & message) ;
120
+ println ! ( "LeEcdsa payload: {payload:?}" ) ;
121
+ }
122
+
123
+ if let Some ( data) = update. payload . le_unsigned {
124
+ // Decode binary data
125
+ let binary_data =
126
+ base64:: engine:: general_purpose:: STANDARD . decode ( & data. data ) ?;
127
+ let message = LeUnsignedMessage :: deserialize_slice ( & binary_data) ?;
128
+
129
+ // Parse the message
130
+ let payload = PayloadData :: deserialize_slice_le ( & message. payload ) ?;
131
+ println ! ( "LE unsigned payload: {payload:?}" ) ;
132
+ }
133
+
134
+ if let Some ( parsed) = update. payload . parsed {
135
+ // Parsed payloads (`parsed: true`) are already decoded and ready to use
136
+ for feed in parsed. price_feeds {
137
+ println ! (
138
+ "Parsed payload: {:?}: {:?} at {:?}" ,
139
+ feed. price_feed_id, feed, parsed. timestamp_us
140
+ ) ;
141
+ }
142
+ }
143
+ }
144
+ msg => println ! ( "Received non-update message: {msg:?}" ) ,
145
+ } ,
146
+ AnyResponse :: Binary ( msg) => {
147
+ println ! ( "Received a binary update for {:?}" , msg. subscription_id) ;
148
+ for message in msg. messages {
149
+ match message {
150
+ Message :: Evm ( message) => {
151
+ // Parse and verify the EVM message
152
+ let payload = parse_and_verify_evm_message ( & message) ;
153
+ println ! ( "EVM payload: {payload:?}" ) ;
154
+ }
155
+ Message :: Solana ( message) => {
156
+ // Parse and verify the Solana message
157
+ let payload = parse_and_verify_solana_message ( & message) ;
158
+ println ! ( "Solana payload: {payload:?}" ) ;
159
+ }
160
+ Message :: LeEcdsa ( message) => {
161
+ let payload = parse_and_verify_le_ecdsa_message ( & message) ;
162
+ println ! ( "LeEcdsa payload: {payload:?}" ) ;
163
+ }
164
+ Message :: LeUnsigned ( message) => {
165
+ let payload = PayloadData :: deserialize_slice_le ( & message. payload ) ?;
166
+ println ! ( "LeUnsigned payload: {payload:?}" ) ;
167
+ }
168
+ Message :: Json ( message) => {
169
+ for feed in message. price_feeds {
170
+ println ! (
171
+ "JSON payload: {:?}: {:?} at {:?}" ,
172
+ feed. price_feed_id, feed, message. timestamp_us
173
+ ) ;
174
+ }
175
+ }
113
176
}
114
177
}
115
178
}
116
- _ => println ! ( "Received non-update message" ) ,
117
179
}
180
+ println ! ( ) ;
118
181
119
182
count += 1 ;
120
183
if count >= 50 {
121
184
break ;
122
185
}
123
186
}
124
187
125
- // Unsubscribe before exiting
188
+ // Unsubscribe example
126
189
for sub_id in [ SubscriptionId ( 1 ) , SubscriptionId ( 2 ) ] {
127
190
client. unsubscribe ( sub_id) . await ?;
128
191
println ! ( "Unsubscribed from {:?}" , sub_id) ;
@@ -147,12 +210,32 @@ fn parse_and_verify_solana_message(solana_message: &SolanaMessage) -> anyhow::Re
147
210
148
211
fn parse_and_verify_evm_message ( evm_message : & EvmMessage ) -> anyhow:: Result < PayloadData > {
149
212
// Recover pubkey from message
150
- libsecp256k1:: recover (
213
+ let public_key = libsecp256k1:: recover (
151
214
& libsecp256k1:: Message :: parse ( & alloy_primitives:: keccak256 ( & evm_message. payload ) ) ,
152
215
& libsecp256k1:: Signature :: parse_standard ( & evm_message. signature ) ?,
153
216
& libsecp256k1:: RecoveryId :: parse ( evm_message. recovery_id ) ?,
154
217
) ?;
218
+ println ! (
219
+ "evm address recovered from signature: {:?}" ,
220
+ hex:: encode( & alloy_primitives:: keccak256( & public_key. serialize( ) [ 1 ..] ) [ 12 ..] )
221
+ ) ;
155
222
156
223
let payload = PayloadData :: deserialize_slice_be ( & evm_message. payload ) ?;
157
224
Ok ( payload)
158
225
}
226
+
227
+ fn parse_and_verify_le_ecdsa_message ( message : & LeEcdsaMessage ) -> anyhow:: Result < PayloadData > {
228
+ // Recover pubkey from message
229
+ let public_key = libsecp256k1:: recover (
230
+ & libsecp256k1:: Message :: parse ( & alloy_primitives:: keccak256 ( & message. payload ) ) ,
231
+ & libsecp256k1:: Signature :: parse_standard ( & message. signature ) ?,
232
+ & libsecp256k1:: RecoveryId :: parse ( message. recovery_id ) ?,
233
+ ) ?;
234
+ println ! (
235
+ "evm address recovered from signature: {:?}" ,
236
+ hex:: encode( & alloy_primitives:: keccak256( & public_key. serialize( ) [ 1 ..] ) [ 12 ..] )
237
+ ) ;
238
+
239
+ let payload = PayloadData :: deserialize_slice_le ( & message. payload ) ?;
240
+ Ok ( payload)
241
+ }
0 commit comments