|
|
@@ -0,0 +1,305 @@
|
|
|
+---
|
|
|
+title: "SVM Searcher Integration"
|
|
|
+description: >-
|
|
|
+ Learn how to integrate Express Relay as a searcher on Solana Virtual Machine chains to fulfill market orders and limit orders.
|
|
|
+---
|
|
|
+
|
|
|
+# SVM Searcher Integration
|
|
|
+
|
|
|
+SVM Express Relay searchers fulfill market order opportunities as well as limit orders on the [Limo](https://solscan.io/account/LiMoM9rMhrdYrfzUCxQppvxCSG1FcrUK9G8uLq4A1GF) program.
|
|
|
+
|
|
|
+## Step 1: Subscribe to New Opportunities
|
|
|
+
|
|
|
+Express Relay provides searchers with [Typescript](https://github.com/pyth-network/per/tree/358eedc1f9072cdfc3418fba309697580f2474f9/sdk/js) and [Python](https://github.com/pyth-network/per/tree/358eedc1f9072cdfc3418fba309697580f2474f9/sdk/python) SDKs to interact with Express Relay. Searchers can also directly fetch available opportunities via HTTP or subscribe to them via WebSocket.
|
|
|
+
|
|
|
+### Typescript SDK
|
|
|
+
|
|
|
+Pyth provides a Typescript SDK, which allows searchers to subscribe to opportunities:
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { Client, Opportunity } from "@pythnetwork/express-relay-js";
|
|
|
+
|
|
|
+const handleOpportunity = async (opportunity: Opportunity) => {
|
|
|
+ console.log("Received opportunity");
|
|
|
+ // Implement your opportunity handler here
|
|
|
+};
|
|
|
+
|
|
|
+const client = new Client(
|
|
|
+ { baseUrl: "https://per-mainnet.dourolabs.app" },
|
|
|
+ undefined, // Default WebSocket options
|
|
|
+ handleOpportunity,
|
|
|
+);
|
|
|
+
|
|
|
+async function main() {
|
|
|
+ await client.subscribeChains(["solana"]);
|
|
|
+}
|
|
|
+
|
|
|
+main();
|
|
|
+```
|
|
|
+
|
|
|
+### Python SDK
|
|
|
+
|
|
|
+Pyth provides a Python SDK, which allows searchers to subscribe to opportunities:
|
|
|
+
|
|
|
+```python
|
|
|
+import asyncio
|
|
|
+from express_relay.client import (
|
|
|
+ ExpressRelayClient,
|
|
|
+)
|
|
|
+from express_relay.models import Opportunity
|
|
|
+
|
|
|
+async def opportunity_callback(opportunity: Opportunity):
|
|
|
+ print("Received opportunity")
|
|
|
+ # Implement your opportunity handler here
|
|
|
+
|
|
|
+client = ExpressRelayClient(
|
|
|
+ "https://per-mainnet.dourolabs.app",
|
|
|
+ None,
|
|
|
+ opportunity_callback,
|
|
|
+ None,
|
|
|
+)
|
|
|
+
|
|
|
+async def main():
|
|
|
+ await client.subscribe_chains(["solana"])
|
|
|
+ task = await client.get_ws_loop()
|
|
|
+ await task
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ asyncio.run(main())
|
|
|
+```
|
|
|
+
|
|
|
+### HTTP API
|
|
|
+
|
|
|
+Searchers can request opportunities through an HTTP **GET** call to the [/v1/opportunities](https://per-mainnet.dourolabs.app/docs#tag/opportunity/operation/get_opportunities) endpoint.
|
|
|
+
|
|
|
+```bash
|
|
|
+curl -X 'GET' \
|
|
|
+ 'https://per-mainnet.dourolabs.app/v1/opportunities?chain_id=solana&mode=live'
|
|
|
+```
|
|
|
+
|
|
|
+Opportunities are short-lived and could be executed in a matter of seconds. So, the above endpoint could return an empty response.
|
|
|
+
|
|
|
+### WebSocket API
|
|
|
+
|
|
|
+Searchers can connect to the server via WebSocket to reduce latency and subscribe to various events. The WebSocket endpoint lives at `/v1/ws` (e.g `wss://per-mainnet.dourolabs.app/v1/ws`). Here is a sample JSON payload to subscribe to opportunities:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "id": "1",
|
|
|
+ "method": "subscribe",
|
|
|
+ "params": {
|
|
|
+ "chain_ids": ["solana"]
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Consult the [Websocket API reference](../websocket-api-reference) for a complete list of methods and parameters.
|
|
|
+
|
|
|
+The server responds with opportunities in the following format:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "order": "UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p...", // The Limo order to be executed, encoded in base64
|
|
|
+ "order_address": "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", // Address of the order account
|
|
|
+ "program": "limo", // Identifier of the program that the order exists in
|
|
|
+ "chain_id": "development-solana",
|
|
|
+ "version": "v1" // Opportunity format version
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## Step 2: Construct the Bid
|
|
|
+
|
|
|
+Searchers should construct a bid by evaluating the fetched opportunity.
|
|
|
+
|
|
|
+> **Warning**: Before constructing the bid, make sure your wallet has the required assets to fulfill the limit order and SOL to pay the bid amount.
|
|
|
+
|
|
|
+See the following examples of how to construct a bid object via the SDKs:
|
|
|
+
|
|
|
+### Typescript SDK
|
|
|
+
|
|
|
+Below is an excerpt of example code. See the full example in the [Typescript SDK](https://github.com/pyth-network/per/blob/4be711525948cf24c0ebd4ebab007dc7f51b7069/sdk/js/src/examples/simpleSearcherLimo.ts).
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { OpportunitySvm } from "../index";
|
|
|
+import { BidSvm } from "../types";
|
|
|
+
|
|
|
+import * as anchor from "@coral-xyz/anchor";
|
|
|
+import * as limo from "@kamino-finance/limo-sdk";
|
|
|
+
|
|
|
+/**
|
|
|
+ * Generates a bid for a given opportunity.
|
|
|
+ * The transaction in this bid transfers assets from the searcher's wallet to fulfill the limit order.
|
|
|
+ * @param opportunity The SVM opportunity to bid on.
|
|
|
+ * @returns The generated bid object.
|
|
|
+ */
|
|
|
+async generateBid(opportunity: OpportunitySvm): Promise<BidSvm> {
|
|
|
+ const order = opportunity.order;
|
|
|
+ const limoClient = new limo.LimoClient(
|
|
|
+ this.connectionSvm,
|
|
|
+ order.state.globalConfig
|
|
|
+ );
|
|
|
+
|
|
|
+ const { bid: bidAmountLamports, transaction } = await this.fulfillLimoOrder(
|
|
|
+ limoClient,
|
|
|
+ order,
|
|
|
+ this.searcherKeypair,
|
|
|
+ this.bidAmountLamports
|
|
|
+ );
|
|
|
+
|
|
|
+ return {
|
|
|
+ amount: bidAmountLamports.toString(),
|
|
|
+ transaction: transaction,
|
|
|
+ valid_until: this.getValidUntil(),
|
|
|
+ };
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Python SDK
|
|
|
+
|
|
|
+Below is an excerpt of example code. See the full example in the [Python SDK](https://github.com/pyth-network/per/blob/4be711525948cf24c0ebd4ebab007dc7f51b7069/sdk/python/express_relay/examples/simple_searcher_limo.py).
|
|
|
+
|
|
|
+```python
|
|
|
+import asyncio
|
|
|
+from typing import Dict, Any
|
|
|
+
|
|
|
+from solders.keypair import Keypair # type: ignore
|
|
|
+from solders.transaction import VersionedTransaction # type: ignore
|
|
|
+
|
|
|
+async def construct_bid(self, opportunity: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
+ """
|
|
|
+ Constructs a bid for the given opportunity.
|
|
|
+ """
|
|
|
+ order = opportunity["order"]
|
|
|
+ order_address = opportunity["order_address"]
|
|
|
+
|
|
|
+ # Calculate bid amount and construct fulfillment transaction
|
|
|
+ bid_amount, transaction = await self.fulfill_limo_order(
|
|
|
+ order, order_address, self.searcher_keypair, self.bid_amount_lamports
|
|
|
+ )
|
|
|
+
|
|
|
+ return {
|
|
|
+ "amount": str(bid_amount),
|
|
|
+ "transaction": transaction,
|
|
|
+ "valid_until": self.get_valid_until(),
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+## Step 3: Submit the Bid
|
|
|
+
|
|
|
+After constructing the bid, searchers should submit it to Express Relay to participate in the auction.
|
|
|
+
|
|
|
+### Typescript SDK
|
|
|
+
|
|
|
+```typescript
|
|
|
+// Submit the bid to Express Relay
|
|
|
+const bidResponse = await client.submitBid(bid);
|
|
|
+console.log("Bid submitted:", bidResponse);
|
|
|
+```
|
|
|
+
|
|
|
+### Python SDK
|
|
|
+
|
|
|
+```python
|
|
|
+# Submit the bid to Express Relay
|
|
|
+bid_response = await client.submit_bid(bid)
|
|
|
+print(f"Bid submitted: {bid_response}")
|
|
|
+```
|
|
|
+
|
|
|
+### HTTP API
|
|
|
+
|
|
|
+Searchers can submit bids through an HTTP **POST** call to the [/v1/bids](https://per-mainnet.dourolabs.app/docs#tag/bid/operation/post_bid) endpoint.
|
|
|
+
|
|
|
+```bash
|
|
|
+curl -X 'POST' \
|
|
|
+ 'https://per-mainnet.dourolabs.app/v1/bids' \
|
|
|
+ -H 'accept: application/json' \
|
|
|
+ -H 'Content-Type: application/json' \
|
|
|
+ -d '{
|
|
|
+ "amount": "1000000",
|
|
|
+ "transaction": "...",
|
|
|
+ "chain_id": "solana",
|
|
|
+ "valid_until": "2024-05-30T13:23:00.000Z"
|
|
|
+ }'
|
|
|
+```
|
|
|
+
|
|
|
+## Step 4: Monitor Bid Results
|
|
|
+
|
|
|
+After submitting a bid, searchers should monitor whether their bid was accepted and the transaction was successfully executed.
|
|
|
+
|
|
|
+### Winning Bids
|
|
|
+
|
|
|
+If your bid wins the auction, Express Relay will execute your transaction on-chain. You can monitor the transaction status through:
|
|
|
+
|
|
|
+- **Transaction hash** returned in the bid response
|
|
|
+- **WebSocket notifications** for bid status updates
|
|
|
+- **HTTP polling** of bid status endpoints
|
|
|
+
|
|
|
+### Failed Bids
|
|
|
+
|
|
|
+If your bid is not selected or execution fails, you'll receive appropriate error notifications. Common reasons for bid failure include:
|
|
|
+
|
|
|
+- **Insufficient bid amount** - Another searcher bid higher
|
|
|
+- **Transaction execution failure** - Insufficient funds or invalid transaction
|
|
|
+- **Timeout** - Bid submitted after opportunity expired
|
|
|
+
|
|
|
+## Error Handling
|
|
|
+
|
|
|
+Implement proper error handling for common scenarios:
|
|
|
+
|
|
|
+### Network Errors
|
|
|
+
|
|
|
+```typescript
|
|
|
+try {
|
|
|
+ const bidResponse = await client.submitBid(bid);
|
|
|
+} catch (error) {
|
|
|
+ if (error.code === "NETWORK_ERROR") {
|
|
|
+ // Retry submission
|
|
|
+ console.log("Network error, retrying...");
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Invalid Opportunities
|
|
|
+
|
|
|
+```typescript
|
|
|
+const handleOpportunity = async (opportunity: Opportunity) => {
|
|
|
+ try {
|
|
|
+ // Validate opportunity before bidding
|
|
|
+ if (!isValidOpportunity(opportunity)) {
|
|
|
+ console.log("Invalid opportunity, skipping");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const bid = await generateBid(opportunity);
|
|
|
+ await client.submitBid(bid);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error processing opportunity:", error);
|
|
|
+ }
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+## Best Practices
|
|
|
+
|
|
|
+### Performance Optimization
|
|
|
+
|
|
|
+- **Use WebSocket connections** for real-time opportunity updates
|
|
|
+- **Implement efficient bid calculation** to minimize latency
|
|
|
+- **Cache frequently used data** like token prices and account information
|
|
|
+
|
|
|
+### Risk Management
|
|
|
+
|
|
|
+- **Set maximum bid amounts** to control potential losses
|
|
|
+- **Implement position limits** to manage overall exposure
|
|
|
+- **Monitor profitability** and adjust strategies accordingly
|
|
|
+
|
|
|
+### Monitoring and Logging
|
|
|
+
|
|
|
+- **Log all opportunities** received for analysis
|
|
|
+- **Track bid success rates** and profitability metrics
|
|
|
+- **Monitor system health** and performance indicators
|
|
|
+
|
|
|
+## Additional Resources
|
|
|
+
|
|
|
+- [Express Relay HTTP API Documentation](../http-api-reference)
|
|
|
+- [WebSocket API Reference](../websocket-api-reference)
|
|
|
+- [Contract Addresses](../contract-addresses)
|
|
|
+- [Error Codes Reference](../errors)
|