SVM Searcher Integration
Learn how to integrate Express Relay as a searcher on Solana Virtual Machine chains to fulfill market orders and limit orders.
SVM Express Relay searchers fulfill market order opportunities as well as limit orders on the Limo program.
Step 1: Subscribe to New Opportunities
Express Relay provides searchers with Typescript and Python SDKs to interact with Express Relay. Searchers can also directly fetch available opportunities via HTTP or subscribe to them via WebSocket.
Pyth provides a Typescript SDK, which allows searchers to subscribe to opportunities:
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();The server responds with opportunities in the following format:
{
"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
}Pyth provides a Python SDK, which allows searchers to subscribe to opportunities:
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())The server responds with opportunities in the following format:
{
"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
}Searchers can request opportunities through an HTTP GET call to the /v1/opportunities endpoint.
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.
The server responds with opportunities in the following format:
{
"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
}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:
{
"id": "1",
"method": "subscribe",
"params": {
"chain_ids": ["solana"]
}
}Consult the Websocket API reference for a complete list of methods and parameters.
The server responds with opportunities in the following format:
{
"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:
Below is an excerpt of example code. See the full example in the Typescript SDK.
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 ixsTakeOrder = await this.generateTakeOrderIxs(limoClient, order);
const feeInstruction = ComputeBudgetProgram.setComputeUnitPrice({
microLamports:
this.latestChainUpdate[this.chainId].latestPrioritizationFee,
});
const txRaw = new anchor.web3.Transaction().add(
feeInstruction,
...ixsTakeOrder
);
const bidAmount = await this.getBidAmount(order);
const config = await this.getExpressRelayConfig();
const bid = await this.client.constructSvmBid(
txRaw,
this.searcher.publicKey,
getPdaAuthority(limoClient.getProgramID(), order.state.globalConfig),
order.address,
bidAmount,
new anchor.BN(Math.round(Date.now() / 1000 + DAY_IN_SECONDS)),
this.chainId,
config.relayerSigner,
config.feeReceiverRelayer
);
bid.transaction.recentBlockhash =
this.latestChainUpdate[this.chainId].blockhash;
bid.transaction.sign(this.searcher);
return bid;
}The bid you construct will look like
{
// serialized transaction object, in base-64 encoding
"transaction": "SGVsbG8sIFdvcmxkIQ==",
"chain_id": "solana",
"env": "svm"
}where the serialized transaction object should contain an Express Relay SubmitBid instruction that specifies the amount you are bidding and the permission details.
Below is an excerpt of example code. See the full example in the Python SDK.
import logging
from solders.transaction import Transaction
from express_relay.models.svm import BidSvm
from express_relay.svm.limo_client import OrderStateAndAddress
DEADLINE = 2**62
logger = logging.getLogger(__name__)
async def generate_bid(self, opp: OpportunitySvm) -> BidSvm:
"""
Generates a bid for a given opportunity.
The transaction in this bid transfers assets from the searcher's wallet to fulfill the limit order.
Args:
opp: The SVM opportunity to bid on.
Returns:
The generated bid object.
"""
order: OrderStateAndAddress = {"address": opp.order_address, "state": opp.order}
ixs_take_order = await self.generate_take_order_ixs(order)
bid_amount = await self.get_bid_amount(order)
router = self.limo_client.get_pda_authority(
self.limo_client.get_program_id(), order["state"].global_config
)
submit_bid_ix = self.client.get_svm_submit_bid_instruction(
searcher=self.private_key.pubkey(),
router=router,
permission_key=order["address"],
bid_amount=bid_amount,
deadline=DEADLINE,
chain_id=self.chain_id,
fee_receiver_relayer=(await self.get_metadata()).fee_receiver_relayer,
relayer_signer=(await self.get_metadata()).relayer_signer,
)
latest_chain_update = self.latest_chain_update[self.chain_id]
fee_instruction = set_compute_unit_price(latest_chain_update.latest_prioritization_fee)
transaction = Transaction.new_with_payer(
[fee_instruction, submit_bid_ix] + ixs_take_order, self.private_key.pubkey()
)
transaction.partial_sign(
[self.private_key], recent_blockhash=latest_chain_update.blockhash
)
bid = BidSvm(transaction=transaction, chain_id=self.chain_id)
return bidThe bid you construct will look like
{
// serialized transaction object, in base-64 encoding
"transaction": "SGVsbG8sIFdvcmxkIQ==",
"chain_id": "solana",
"env": "svm"
}where the serialized transaction object should contain an Express Relay SubmitBid instruction that specifies the amount you are bidding and the permission details.
Step 3: Submit Bids on Opportunities to Express Relay
Searchers can submit their constructed bids to Express Relay via the SDKs, an HTTP POST request, or a WebSocket connection.
The code snippet below demonstrates how to submit a bid using the Typescript SDK:
const generateBid = async (opportunity: OpportunitySvm, recentBlockhash: Blockhash): BidSvm => {
...
}
const handleOpportunity = async (opportunity: Opportunity) => {
...
const bid = await this.generateBid(opportunity as OpportunitySvm);
await client.submitBid(bid);
}The code snippet below demonstrates how to submit a bid using the Python SDK:
import typing
async def generate_bid(opp: OpportunitySvm) -> BidSvm:
...
def opportunity_callback(opportunity: Opportunity):
bid = await self.assess_opportunity(typing.cast(OpportunitySvm, opp))
await client.submit_bid(bid, subscribe_to_updates=True)Searchers can submit bids through an HTTP POST call to the /v1/bids endpoint. This endpoint accepts a JSON payload containing the details of the bid.
curl -X 'POST' \
'https://per-mainnet.dourolabs.app/v1/bids' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"chain_id": "solana",
"transaction": "SGVsbG8sIFdvcmxkIQ=="
}'Searchers can submit bids via Websocket to avoid additional network round-trips and get notified about changes to the bid status.
{
"id": "1",
"method": "post_bid",
"params": {
"bid": {
"chain_id": "solana",
"transaction": "SGVsbG8sIFdvcmxkIQ=="
}
}
}A successful response to a bid submission has the following schema:
{
"id": "1", // Websocket request id
"status": "success",
"result": {
"id": "beedbeed-b346-4fa1-8fab-2541a9e1872d", // Bid id
"status": "OK"
}
}Consult Websocket API reference for more details.
How to Integrate Express Relay as a Searcher
Express Relay allows searchers to integrate once and access all existing and future opportunities across integrated DeFi protocols. Searchers bid on these opportunities exposed by Express Relay.
HTTP API Reference
Complete HTTP API reference for Express Relay endpoints, including opportunities, bids, and configuration.