@@ -2,7 +2,7 @@ import { Callout, Tabs, Steps } from "nextra/components";
2
2
3
3
# SVM Searcher Integration
4
4
5
- SVM Express Relay searchers fulfill opportunities representing limit orders on the [ Limo] ( ) program.
5
+ SVM Express Relay searchers fulfill opportunities representing limit orders on the [ Limo] ( https://solscan.io/account/LiMoM9rMhrdYrfzUCxQppvxCSG1FcrUK9G8uLq4A1GF ) program.
6
6
7
7
<Steps >
8
8
@@ -19,39 +19,53 @@ Pyth provides a Typescript SDK, which allows searchers to subscribe to opportuni
19
19
``` typescript
20
20
import { Client , Opportunity } from " @pythnetwork/express-relay-js" ;
21
21
22
- const handleOpporunity = async (opportunity : Opportunity ) => {
22
+ const handleOpportunity = async (opportunity : Opportunity ) => {
23
+ console .log (" Received opportunity" );
23
24
// Implement your opportunity handler here
24
25
};
25
26
26
27
const client = new Client (
27
28
{ baseUrl: " https://pyth-express-relay-mainnet.asymmetric.re" },
28
29
undefined , // Default WebSocket options
29
- handleOpporunity
30
+ handleOpportunity
30
31
);
31
- await client .subscribeChains ([" development-solana" ]);
32
+
33
+ async function main() {
34
+ await client .subscribeChains ([" solana" ]);
35
+ }
36
+
37
+ main ();
32
38
```
33
39
34
40
</Tabs.Tab >
35
41
<Tabs.Tab >
36
42
Pyth provides a Python SDK, which allows searchers to subscribe to opportunities:
37
43
38
44
``` python copy
45
+ import asyncio
39
46
from express_relay.client import (
40
47
ExpressRelayClient,
41
48
)
42
- from express_relay.express_relay_types import Opportunity
49
+ from express_relay.models import Opportunity
43
50
44
- def opportunity_callback (opportunity : Opportunity):
51
+ async def opportunity_callback (opportunity : Opportunity):
52
+ print (" Received opportunity" )
45
53
# Implement your opportunity handler here
46
- pass
47
54
48
55
client = ExpressRelayClient(
49
- " https://per-staging.dourolabs.app " ,
56
+ " https://pyth-express-relay-mainnet.asymmetric.re " ,
50
57
None ,
51
58
opportunity_callback,
52
59
None ,
53
60
)
54
- await client.subscribe_chains([' development-solana' ])
61
+
62
+ async def main ():
63
+ await client.subscribe_chains([" solana" ])
64
+ task = await client.get_ws_loop()
65
+ await task
66
+
67
+ if __name__ == " __main__" :
68
+ asyncio.run(main())
55
69
```
56
70
57
71
</Tabs.Tab >
@@ -60,7 +74,7 @@ Searchers can request opportunities through an HTTP **GET** call to the [`/v1/op
60
74
61
75
``` bash copy
62
76
curl -X ' GET' \
63
- ' https://pyth-express-relay-mainnet.asymmetric.re/v1/opportunities?chain_id=development- solana&mode=live'
77
+ ' https://pyth-express-relay-mainnet.asymmetric.re/v1/opportunities?chain_id=solana&mode=live'
64
78
```
65
79
66
80
Opportunities are short-lived and could be executed in a matter of seconds. So, the above endpoint could return an empty response.
@@ -75,7 +89,7 @@ Here is a sample JSON payload to subscribe to opportunities:
75
89
" id" : " 1" ,
76
90
" method" : " subscribe" ,
77
91
" params" : {
78
- " chain_ids" : [" development- solana" ]
92
+ " chain_ids" : [" solana" ]
79
93
}
80
94
}
81
95
```
@@ -125,32 +139,41 @@ import * as limo from "@kamino-finance/limo-sdk";
125
139
* @returns The generated bid object
126
140
*/
127
141
async generateBid (opportunity : OpportunitySvm ): Promise < BidSvm > {
128
- const order = opportunity .order ;
129
- const limoClient = new limo .LimoClient (
130
- this .connectionSvm ,
131
- order .state .globalConfig
132
- );
133
-
134
- const ixsTakeOrder = await this .generateTakeOrderIxs (limoClient , order );
135
- const txRaw = new anchor .web3 .Transaction ().add (... ixsTakeOrder );
136
-
137
- const bidData = await this .getBidData (limoClient , order );
138
-
139
- const bid = await this .client .constructSvmBid (
140
- txRaw ,
141
- this .searcher .publicKey ,
142
- bidData .router ,
143
- order .address ,
144
- bidData .bidAmount ,
145
- new anchor .BN (Math .round (Date .now () / 1000 + DAY_IN_SECONDS )),
146
- this .chainId ,
147
- bidData .relayerSigner ,
148
- bidData .relayerFeeReceiver
149
- );
150
-
151
- bid.transaction.recentBlockhash = this .recentBlockhash [this .chainId ];
152
- bid.transaction.sign(this.searcher);
153
- return bid;
142
+ const order = opportunity .order ;
143
+ const limoClient = new limo .LimoClient (
144
+ this .connectionSvm ,
145
+ order .state .globalConfig
146
+ );
147
+
148
+ const ixsTakeOrder = await this .generateTakeOrderIxs (limoClient , order );
149
+ const feeInstruction = ComputeBudgetProgram .setComputeUnitPrice ({
150
+ microLamports:
151
+ this .latestChainUpdate [this .chainId ].latestPrioritizationFee ,
152
+ });
153
+ const txRaw = new anchor .web3 .Transaction ().add (
154
+ feeInstruction ,
155
+ ... ixsTakeOrder
156
+ );
157
+
158
+ const bidAmount = await this .getBidAmount (order );
159
+
160
+ const config = await this .getExpressRelayConfig ();
161
+ const bid = await this .client .constructSvmBid (
162
+ txRaw ,
163
+ this .searcher .publicKey ,
164
+ getPdaAuthority (limoClient .getProgramID (), order .state .globalConfig ),
165
+ order .address ,
166
+ bidAmount ,
167
+ new anchor .BN (Math .round (Date .now () / 1000 + DAY_IN_SECONDS )),
168
+ this .chainId ,
169
+ config .relayerSigner ,
170
+ config .feeReceiverRelayer
171
+ );
172
+
173
+ bid.transaction.recentBlockhash =
174
+ this.latestChainUpdate[this .chainId ].blockhash;
175
+ bid.transaction.sign(this.searcher);
176
+ return bid;
154
177
}
155
178
```
156
179
@@ -179,24 +202,32 @@ async def assess_opportunity(self, opp: OpportunitySvm) -> BidSvm | None:
179
202
A bid object if the opportunity is worth taking to be submitted to the Express Relay server, otherwise None.
180
203
"""
181
204
order: OrderStateAndAddress = {"address": opp.order_address, "state": opp.order}
205
+
182
206
ixs_take_order = await self.generate_take_order_ixs(order)
183
- bid_data = await self .get_bid_data(order)
207
+ bid_amount = await self.get_bid_amount(order)
208
+ router = self.limo_client.get_pda_authority(
209
+ self.limo_client.get_program_id(), order["state"].global_config
210
+ )
184
211
185
212
submit_bid_ix = self.client.get_svm_submit_bid_instruction(
186
213
searcher=self.private_key.pubkey(),
187
- router = bid_data. router,
214
+ router=router,
188
215
permission_key=order["address"],
189
- bid_amount = bid_data. bid_amount,
216
+ bid_amount=bid_amount,
190
217
deadline=DEADLINE,
191
218
chain_id=self.chain_id,
192
- fee_receiver_relayer = bid_data.relayer_fee_receiver,
193
- relayer_signer = bid_data.relayer_signer,
219
+ fee_receiver_relayer=(await self.get_metadata()).fee_receiver_relayer,
220
+ relayer_signer=(await self.get_metadata()).relayer_signer,
221
+ )
222
+ latest_chain_update = self.latest_chain_update[self.chain_id]
223
+ fee_instruction = set_compute_unit_price(
224
+ latest_chain_update.latest_prioritization_fee
194
225
)
195
226
transaction = Transaction.new_with_payer(
196
- [submit_bid_ix] + ixs_take_order, self .private_key.pubkey()
227
+ [fee_instruction, submit_bid_ix] + ixs_take_order, self.private_key.pubkey()
197
228
)
198
229
transaction.partial_sign(
199
- [self .private_key], recent_blockhash = self .recent_blockhash[ self .chain_id]
230
+ [self.private_key], recent_blockhash=latest_chain_update.blockhash
200
231
)
201
232
bid = BidSvm(transaction=transaction, chain_id=self.chain_id)
202
233
return bid
@@ -233,9 +264,9 @@ const generateBid = async (opportunity: OpportunitySvm, recentBlockhash: Blockha
233
264
...
234
265
}
235
266
236
- const handleOpporunity = async (opportunity : Opportunity ) => {
267
+ const handleOpportunity = async (opportunity: Opportunity) => {
237
268
...
238
- const bid = await generateBid (opportunity as OpportunitySvm , this . recentBlockhash [ this . chainId ] );
269
+ const bid = await this. generateBid(opportunity as OpportunitySvm);
239
270
await client.submitBid(bid);
240
271
}
241
272
` ` `
@@ -252,7 +283,7 @@ async def generate_bid(opp: OpportunitySvm) -> BidSvm:
252
283
...
253
284
254
285
def opportunity_callback(opportunity: Opportunity):
255
- bid = generate_bid (typing.cast(OpportunitySvm, opportunity ))
286
+ bid = await self.assess_opportunity (typing.cast(OpportunitySvm, opp ))
256
287
await client.submit_bid(bid, subscribe_to_updates=True)
257
288
` ` `
258
289
@@ -264,7 +295,7 @@ Searchers can submit bids through an HTTP POST call to the [`/v1/bids`](https://
264
295
curl -X POST https://pyth-express-relay-mainnet.asymmetric.re/v1/bids \
265
296
-H "Content-Type: application/json" \
266
297
-d '{
267
- "chain_id": "development- solana",
298
+ "chain_id": "solana",
268
299
"transaction": "SGVsbG8sIFdvcmxkIQ=="
269
300
}'
270
301
` ` `
@@ -280,7 +311,7 @@ Searchers can submit bids via Websocket to avoid additional network round-trips
280
311
"method": "post_bid",
281
312
"params": {
282
313
"bid": {
283
- " chain_id" : " development- solana" ,
314
+ "chain_id": "solana",
284
315
"transaction": "SGVsbG8sIFdvcmxkIQ=="
285
316
}
286
317
}
0 commit comments