Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions express_relay/sdk/js/src/examples/simpleSearcherLimo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@ import { Keypair, PublicKey, Connection } from "@solana/web3.js";

import * as limo from "@kamino-finance/limo-sdk";
import { Decimal } from "decimal.js";
import { getPdaAuthority } from "@kamino-finance/limo-sdk/dist/utils";
import {
getMintDecimals,
getPdaAuthority,
} from "@kamino-finance/limo-sdk/dist/utils";

const DAY_IN_SECONDS = 60 * 60 * 24;

class SimpleSearcherLimo {
private client: Client;
private connectionSvm: Connection;
private clientLimo: limo.LimoClient;
private readonly connectionSvm: Connection;
private mintDecimals: Record<string, number> = {};
private expressRelayConfig: ExpressRelaySvmConfig | undefined;
constructor(
public endpointExpressRelay: string,
public chainId: string,
private searcher: Keypair,
public endpointSvm: string,
public globalConfig: PublicKey,
public fillRate: number,
public apiKey?: string
) {
Expand All @@ -42,7 +44,6 @@ class SimpleSearcherLimo {
this.bidStatusHandler.bind(this)
);
this.connectionSvm = new Connection(endpointSvm, "confirmed");
this.clientLimo = new limo.LimoClient(this.connectionSvm, globalConfig);
}

async bidStatusHandler(bidStatus: BidStatusUpdate) {
Expand All @@ -59,13 +60,27 @@ class SimpleSearcherLimo {
);
}

async getMintDecimalsCached(mint: PublicKey): Promise<number> {
const mintAddress = mint.toBase58();
if (this.mintDecimals[mintAddress]) {
return this.mintDecimals[mintAddress];
}
const decimals = await getMintDecimals(this.connectionSvm, mint);
this.mintDecimals[mintAddress] = decimals;
return decimals;
}

async generateBid(opportunity: OpportunitySvm) {
const order = opportunity.order;
const inputMintDecimals = await this.clientLimo.getOrderInputMintDecimals(
order
const limoClient = new limo.LimoClient(
this.connectionSvm,
order.state.globalConfig
);
const outputMintDecimals = await this.clientLimo.getOrderOutputMintDecimals(
order
const inputMintDecimals = await this.getMintDecimalsCached(
order.state.inputMint
);
const outputMintDecimals = await this.getMintDecimalsCached(
order.state.outputMint
);
const inputAmountDecimals = new Decimal(
order.state.remainingInputAmount.toNumber()
Expand Down Expand Up @@ -96,7 +111,7 @@ class SimpleSearcherLimo {
outputAmountDecimals.toString()
);

const ixsTakeOrder = await this.clientLimo.takeOrderIx(
const ixsTakeOrder = await limoClient.takeOrderIx(
this.searcher.publicKey,
order,
inputAmountDecimals,
Expand All @@ -107,7 +122,7 @@ class SimpleSearcherLimo {
const txRaw = new anchor.web3.Transaction().add(...ixsTakeOrder);

const router = getPdaAuthority(
this.clientLimo.getProgramID(),
limoClient.getProgramID(),
order.state.globalConfig
);
const bidAmount = new anchor.BN(argv.bid);
Expand Down Expand Up @@ -174,11 +189,6 @@ const argv = yargs(hideBin(process.argv))
type: "string",
demandOption: true,
})
.option("global-config", {
description: "Global config address",
type: "string",
demandOption: true,
})
.option("bid", {
description: "Bid amount in lamports",
type: "string",
Expand Down Expand Up @@ -242,7 +252,6 @@ async function run() {
argv.chainId,
searcherKeyPair,
argv.endpointSvm,
new PublicKey(argv.globalConfig),
argv.fillRate,
argv.apiKey
);
Expand Down
3 changes: 2 additions & 1 deletion express_relay/sdk/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import * as evm from "./evm";
import * as svm from "./svm";

export * from "./types";
export * from "./const";

export class ClientError extends Error {}

Expand Down Expand Up @@ -400,7 +401,7 @@ export class Client {
* @param opportunity
* @returns Opportunity in the converted client format
*/
private convertOpportunity(
public convertOpportunity(
opportunity: components["schemas"]["Opportunity"]
): Opportunity | undefined {
if (opportunity.version !== "v1") {
Expand Down
59 changes: 34 additions & 25 deletions express_relay/sdk/js/src/serverTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export interface components {
* @example beedbeed-58cc-4372-a567-0e02b2c3d479
*/
id: string;
/**
* @description The status of the request. If the bid was placed successfully, the status will be "OK".
* @example OK
*/
status: string;
};
BidStatus:
Expand Down Expand Up @@ -464,11 +468,6 @@ export interface components {
| components["schemas"]["SimulatedBidSvm"];
/** BidResponseEvm */
SimulatedBidEvm: {
/**
* @description Amount of bid in wei.
* @example 10
*/
bid_amount: string;
/**
* @description The chain id for bid.
* @example op_sepolia
Expand All @@ -484,23 +483,28 @@ export interface components {
* @example 2024-05-23T21:26:57.329954Z
*/
initiation_time: string;
/**
* @description The permission key for bid.
* @example 0xdeadbeef
*/
permission_key: string;
/**
* @description The profile id for the bid owner.
* @example
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
*/
profile_id: string;
status: components["schemas"]["BidStatus"];
} & {
/**
* @description Amount of bid in wei.
* @example 10
*/
bid_amount: string;
/**
* @description The gas limit for the contract call.
* @example 2000000
*/
gas_limit: string;
/**
* @description The permission key for bid.
* @example 0xdeadbeef
*/
permission_key: string;
status: components["schemas"]["BidStatusEvm"];
/**
* @description Calldata for the contract call.
* @example 0xdeadbeef
Expand All @@ -514,11 +518,6 @@ export interface components {
};
/** BidResponseSvm */
SimulatedBidSvm: {
/**
* @description Amount of bid in wei.
* @example 10
*/
bid_amount: string;
/**
* @description The chain id for bid.
* @example op_sepolia
Expand All @@ -534,18 +533,25 @@ export interface components {
* @example 2024-05-23T21:26:57.329954Z
*/
initiation_time: string;
/**
* @description The permission key for bid.
* @example 0xdeadbeef
*/
permission_key: string;
/**
* @description The profile id for the bid owner.
* @example
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
*/
profile_id: string;
status: components["schemas"]["BidStatus"];
} & {
/**
* Format: int64
* @description Amount of bid in lamports.
* @example 1000
*/
bid_amount: number;
/**
* @description The permission key for bid in base64 format.
* This is the concatenation of the permission account and the router account.
* @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5
*/
permission_key: string;
status: components["schemas"]["BidStatusSvm"];
/**
* @description The transaction of the bid.
* @example SGVsbG8sIFdvcmxkIQ==
Expand Down Expand Up @@ -591,6 +597,10 @@ export interface components {
* @example beedbeed-58cc-4372-a567-0e02b2c3d479
*/
id: string;
/**
* @description The status of the request. If the bid was placed successfully, the status will be "OK".
* @example OK
*/
status: string;
};
};
Expand Down Expand Up @@ -686,7 +696,6 @@ export interface operations {
};
};
responses: {
/** @description Latest status of the bid */
200: {
content: {
"application/json": components["schemas"]["BidStatus"];
Expand Down
24 changes: 21 additions & 3 deletions express_relay/sdk/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,28 @@ $ poetry add express-relay

## Quickstart

To run the simple searcher script, navigate to `python/` and run
To run the simple searcher script, navigate to `python/` and run the following command:

### Evm

```
$ poetry run python3 -m express_relay.searcher.examples.simple_searcher --private-key <PRIVATE_KEY_HEX_STRING> --chain-id development --verbose --server-url https://per-staging.dourolabs.app/
$ poetry run python3 -m express_relay.searcher.examples.simple_searcher_evm \
--private-key <PRIVATE_KEY_HEX_STRING> \
--chain-id development \
--verbose \
--server-url https://per-staging.dourolabs.app/
```

This simple example runs a searcher that queries the Express Relay liquidation server for available liquidation opportunities and naively submits a bid on each available opportunity.
This simple example runs a searcher that queries the Express Relay liquidation server for available liquidation
opportunities and naively submits a bid on each available opportunity.

### Svm

```
$ poetry run python3 -m express_relay.searcher.examples.simple_searcher_svm \
--endpoint-express-relay https://per-staging.dourolabs.app/ \
--chain-id development-solana \
--private-key-json-file <PATH_TO_JSON_FILE> \
--endpoint-svm https://api.mainnet-beta.solana.com \
--bid 10000000 # Bid amount in lamports
```
50 changes: 28 additions & 22 deletions express_relay/sdk/python/express_relay/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import json
import urllib.parse
import warnings
from asyncio import Task
from collections.abc import Coroutine
from datetime import datetime
Expand All @@ -10,7 +11,7 @@
import httpx
import web3
import websockets
from eth_abi import encode
from eth_abi.abi import encode
from eth_account.account import Account
from eth_account.datastructures import SignedMessage
from eth_utils import to_checksum_address
Expand All @@ -25,27 +26,33 @@
EXECUTION_PARAMS_TYPESTRING,
SVM_CONFIGS,
)
from express_relay.express_relay_types import (
BidResponse,
Opportunity,
BidStatusUpdate,
ClientMessage,
Bid,
OpportunityBid,
OpportunityParams,
from express_relay.models.evm import (
Address,
Bytes32,
TokenAmount,
OpportunityBidParams,
BidEvm,
)
from express_relay.models import (
Bid,
BidStatusUpdate,
BidResponse,
OpportunityBidParams,
OpportunityBid,
OpportunityParams,
Opportunity,
OpportunityRoot,
OpportunityEvm,
ClientMessage,
BidResponseRoot,
)
from express_relay.svm.generated.express_relay.instructions import submit_bid
from express_relay.models.base import UnsupportedOpportunityVersionException
from express_relay.models.evm import OpportunityEvm
from express_relay.svm.generated.express_relay.instructions.submit_bid import submit_bid
from express_relay.svm.generated.express_relay.program_id import (
PROGRAM_ID as SVM_EXPRESS_RELAY_PROGRAM_ID,
)
from express_relay.svm.generated.express_relay.types import SubmitBidArgs
from express_relay.svm.generated.express_relay.types.submit_bid_args import (
SubmitBidArgs,
)
from express_relay.svm.limo_client import LimoClient


Expand Down Expand Up @@ -334,11 +341,10 @@ async def ws_handler(

elif msg_json.get("type") == "bid_status_update":
if bid_status_callback is not None:
bid_status_update = BidStatusUpdate.process_bid_status_dict(
bid_status_update = BidStatusUpdate.model_validate(
msg_json["status"]
)
if bid_status_update:
asyncio.create_task(bid_status_callback(bid_status_update))
asyncio.create_task(bid_status_callback(bid_status_update))

elif msg_json.get("id"):
future = self.ws_msg_futures.pop(msg_json["id"])
Expand Down Expand Up @@ -369,10 +375,11 @@ async def get_opportunities(self, chain_id: str | None = None) -> list[Opportuni

opportunities: list[Opportunity] = []
for opportunity in resp.json():
opportunity_processed = OpportunityRoot.model_validate(opportunity)
if opportunity_processed:
try:
opportunity_processed = OpportunityRoot.model_validate(opportunity)
opportunities.append(opportunity_processed.root)

except UnsupportedOpportunityVersionException as e:
warnings.warn(str(e))
return opportunities

async def submit_opportunity(self, opportunity: OpportunityParams) -> UUID:
Expand Down Expand Up @@ -419,9 +426,8 @@ async def get_bids(self, from_time: datetime | None = None) -> list[BidResponse]

bids = []
for bid in resp.json()["items"]:
bid_processed = BidResponse.process_bid_response_dict(bid)
if bid_processed:
bids.append(bid_processed)
bid_processed = BidResponseRoot.model_validate(bid)
bids.append(bid_processed.root)

return bids

Expand Down
2 changes: 1 addition & 1 deletion express_relay/sdk/python/express_relay/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from solders.pubkey import Pubkey

from express_relay.express_relay_types import OpportunityAdapterConfig
from express_relay.models import OpportunityAdapterConfig

OPPORTUNITY_ADAPTER_CONFIGS = {
"op_sepolia": OpportunityAdapterConfig(
Expand Down
Loading
Loading