Skip to content

Commit ed98240

Browse files
authored
Fix: improvements on express relay sdks and simple searchers (#2045)
* feat: Cache mint decimals and use global config from the order * Improve svm types * Minor updates on ts package
1 parent c7d5cf0 commit ed98240

File tree

14 files changed

+677
-619
lines changed

14 files changed

+677
-619
lines changed

express_relay/sdk/js/src/examples/simpleSearcherLimo.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,23 @@ import { Keypair, PublicKey, Connection } from "@solana/web3.js";
1414

1515
import * as limo from "@kamino-finance/limo-sdk";
1616
import { Decimal } from "decimal.js";
17-
import { getPdaAuthority } from "@kamino-finance/limo-sdk/dist/utils";
17+
import {
18+
getMintDecimals,
19+
getPdaAuthority,
20+
} from "@kamino-finance/limo-sdk/dist/utils";
1821

1922
const DAY_IN_SECONDS = 60 * 60 * 24;
2023

2124
class SimpleSearcherLimo {
2225
private client: Client;
23-
private connectionSvm: Connection;
24-
private clientLimo: limo.LimoClient;
26+
private readonly connectionSvm: Connection;
27+
private mintDecimals: Record<string, number> = {};
2528
private expressRelayConfig: ExpressRelaySvmConfig | undefined;
2629
constructor(
2730
public endpointExpressRelay: string,
2831
public chainId: string,
2932
private searcher: Keypair,
3033
public endpointSvm: string,
31-
public globalConfig: PublicKey,
3234
public fillRate: number,
3335
public apiKey?: string
3436
) {
@@ -42,7 +44,6 @@ class SimpleSearcherLimo {
4244
this.bidStatusHandler.bind(this)
4345
);
4446
this.connectionSvm = new Connection(endpointSvm, "confirmed");
45-
this.clientLimo = new limo.LimoClient(this.connectionSvm, globalConfig);
4647
}
4748

4849
async bidStatusHandler(bidStatus: BidStatusUpdate) {
@@ -59,13 +60,27 @@ class SimpleSearcherLimo {
5960
);
6061
}
6162

63+
async getMintDecimalsCached(mint: PublicKey): Promise<number> {
64+
const mintAddress = mint.toBase58();
65+
if (this.mintDecimals[mintAddress]) {
66+
return this.mintDecimals[mintAddress];
67+
}
68+
const decimals = await getMintDecimals(this.connectionSvm, mint);
69+
this.mintDecimals[mintAddress] = decimals;
70+
return decimals;
71+
}
72+
6273
async generateBid(opportunity: OpportunitySvm) {
6374
const order = opportunity.order;
64-
const inputMintDecimals = await this.clientLimo.getOrderInputMintDecimals(
65-
order
75+
const limoClient = new limo.LimoClient(
76+
this.connectionSvm,
77+
order.state.globalConfig
6678
);
67-
const outputMintDecimals = await this.clientLimo.getOrderOutputMintDecimals(
68-
order
79+
const inputMintDecimals = await this.getMintDecimalsCached(
80+
order.state.inputMint
81+
);
82+
const outputMintDecimals = await this.getMintDecimalsCached(
83+
order.state.outputMint
6984
);
7085
const inputAmountDecimals = new Decimal(
7186
order.state.remainingInputAmount.toNumber()
@@ -96,7 +111,7 @@ class SimpleSearcherLimo {
96111
outputAmountDecimals.toString()
97112
);
98113

99-
const ixsTakeOrder = await this.clientLimo.takeOrderIx(
114+
const ixsTakeOrder = await limoClient.takeOrderIx(
100115
this.searcher.publicKey,
101116
order,
102117
inputAmountDecimals,
@@ -107,7 +122,7 @@ class SimpleSearcherLimo {
107122
const txRaw = new anchor.web3.Transaction().add(...ixsTakeOrder);
108123

109124
const router = getPdaAuthority(
110-
this.clientLimo.getProgramID(),
125+
limoClient.getProgramID(),
111126
order.state.globalConfig
112127
);
113128
const bidAmount = new anchor.BN(argv.bid);
@@ -174,11 +189,6 @@ const argv = yargs(hideBin(process.argv))
174189
type: "string",
175190
demandOption: true,
176191
})
177-
.option("global-config", {
178-
description: "Global config address",
179-
type: "string",
180-
demandOption: true,
181-
})
182192
.option("bid", {
183193
description: "Bid amount in lamports",
184194
type: "string",
@@ -242,7 +252,6 @@ async function run() {
242252
argv.chainId,
243253
searcherKeyPair,
244254
argv.endpointSvm,
245-
new PublicKey(argv.globalConfig),
246255
argv.fillRate,
247256
argv.apiKey
248257
);

express_relay/sdk/js/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import * as evm from "./evm";
3131
import * as svm from "./svm";
3232

3333
export * from "./types";
34+
export * from "./const";
3435

3536
export class ClientError extends Error {}
3637

@@ -400,7 +401,7 @@ export class Client {
400401
* @param opportunity
401402
* @returns Opportunity in the converted client format
402403
*/
403-
private convertOpportunity(
404+
public convertOpportunity(
404405
opportunity: components["schemas"]["Opportunity"]
405406
): Opportunity | undefined {
406407
if (opportunity.version !== "v1") {

express_relay/sdk/js/src/serverTypes.d.ts

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export interface components {
8888
* @example beedbeed-58cc-4372-a567-0e02b2c3d479
8989
*/
9090
id: string;
91+
/**
92+
* @description The status of the request. If the bid was placed successfully, the status will be "OK".
93+
* @example OK
94+
*/
9195
status: string;
9296
};
9397
BidStatus:
@@ -464,11 +468,6 @@ export interface components {
464468
| components["schemas"]["SimulatedBidSvm"];
465469
/** BidResponseEvm */
466470
SimulatedBidEvm: {
467-
/**
468-
* @description Amount of bid in wei.
469-
* @example 10
470-
*/
471-
bid_amount: string;
472471
/**
473472
* @description The chain id for bid.
474473
* @example op_sepolia
@@ -484,23 +483,28 @@ export interface components {
484483
* @example 2024-05-23T21:26:57.329954Z
485484
*/
486485
initiation_time: string;
487-
/**
488-
* @description The permission key for bid.
489-
* @example 0xdeadbeef
490-
*/
491-
permission_key: string;
492486
/**
493487
* @description The profile id for the bid owner.
494-
* @example
488+
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
495489
*/
496490
profile_id: string;
497-
status: components["schemas"]["BidStatus"];
498491
} & {
492+
/**
493+
* @description Amount of bid in wei.
494+
* @example 10
495+
*/
496+
bid_amount: string;
499497
/**
500498
* @description The gas limit for the contract call.
501499
* @example 2000000
502500
*/
503501
gas_limit: string;
502+
/**
503+
* @description The permission key for bid.
504+
* @example 0xdeadbeef
505+
*/
506+
permission_key: string;
507+
status: components["schemas"]["BidStatusEvm"];
504508
/**
505509
* @description Calldata for the contract call.
506510
* @example 0xdeadbeef
@@ -514,11 +518,6 @@ export interface components {
514518
};
515519
/** BidResponseSvm */
516520
SimulatedBidSvm: {
517-
/**
518-
* @description Amount of bid in wei.
519-
* @example 10
520-
*/
521-
bid_amount: string;
522521
/**
523522
* @description The chain id for bid.
524523
* @example op_sepolia
@@ -534,18 +533,25 @@ export interface components {
534533
* @example 2024-05-23T21:26:57.329954Z
535534
*/
536535
initiation_time: string;
537-
/**
538-
* @description The permission key for bid.
539-
* @example 0xdeadbeef
540-
*/
541-
permission_key: string;
542536
/**
543537
* @description The profile id for the bid owner.
544-
* @example
538+
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
545539
*/
546540
profile_id: string;
547-
status: components["schemas"]["BidStatus"];
548541
} & {
542+
/**
543+
* Format: int64
544+
* @description Amount of bid in lamports.
545+
* @example 1000
546+
*/
547+
bid_amount: number;
548+
/**
549+
* @description The permission key for bid in base64 format.
550+
* This is the concatenation of the permission account and the router account.
551+
* @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5
552+
*/
553+
permission_key: string;
554+
status: components["schemas"]["BidStatusSvm"];
549555
/**
550556
* @description The transaction of the bid.
551557
* @example SGVsbG8sIFdvcmxkIQ==
@@ -591,6 +597,10 @@ export interface components {
591597
* @example beedbeed-58cc-4372-a567-0e02b2c3d479
592598
*/
593599
id: string;
600+
/**
601+
* @description The status of the request. If the bid was placed successfully, the status will be "OK".
602+
* @example OK
603+
*/
594604
status: string;
595605
};
596606
};
@@ -686,7 +696,6 @@ export interface operations {
686696
};
687697
};
688698
responses: {
689-
/** @description Latest status of the bid */
690699
200: {
691700
content: {
692701
"application/json": components["schemas"]["BidStatus"];

express_relay/sdk/python/README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,28 @@ $ poetry add express-relay
1616

1717
## Quickstart
1818

19-
To run the simple searcher script, navigate to `python/` and run
19+
To run the simple searcher script, navigate to `python/` and run the following command:
20+
21+
### Evm
2022

2123
```
22-
$ 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/
24+
$ poetry run python3 -m express_relay.searcher.examples.simple_searcher_evm \
25+
--private-key <PRIVATE_KEY_HEX_STRING> \
26+
--chain-id development \
27+
--verbose \
28+
--server-url https://per-staging.dourolabs.app/
2329
```
2430

25-
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.
31+
This simple example runs a searcher that queries the Express Relay liquidation server for available liquidation
32+
opportunities and naively submits a bid on each available opportunity.
33+
34+
### Svm
35+
36+
```
37+
$ poetry run python3 -m express_relay.searcher.examples.simple_searcher_svm \
38+
--endpoint-express-relay https://per-staging.dourolabs.app/ \
39+
--chain-id development-solana \
40+
--private-key-json-file <PATH_TO_JSON_FILE> \
41+
--endpoint-svm https://api.mainnet-beta.solana.com \
42+
--bid 10000000 # Bid amount in lamports
43+
```

express_relay/sdk/python/express_relay/client.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import json
33
import urllib.parse
4+
import warnings
45
from asyncio import Task
56
from collections.abc import Coroutine
67
from datetime import datetime
@@ -10,7 +11,7 @@
1011
import httpx
1112
import web3
1213
import websockets
13-
from eth_abi import encode
14+
from eth_abi.abi import encode
1415
from eth_account.account import Account
1516
from eth_account.datastructures import SignedMessage
1617
from eth_utils import to_checksum_address
@@ -25,27 +26,33 @@
2526
EXECUTION_PARAMS_TYPESTRING,
2627
SVM_CONFIGS,
2728
)
28-
from express_relay.express_relay_types import (
29-
BidResponse,
30-
Opportunity,
31-
BidStatusUpdate,
32-
ClientMessage,
33-
Bid,
34-
OpportunityBid,
35-
OpportunityParams,
29+
from express_relay.models.evm import (
3630
Address,
3731
Bytes32,
3832
TokenAmount,
39-
OpportunityBidParams,
4033
BidEvm,
34+
)
35+
from express_relay.models import (
36+
Bid,
37+
BidStatusUpdate,
38+
BidResponse,
39+
OpportunityBidParams,
40+
OpportunityBid,
41+
OpportunityParams,
42+
Opportunity,
4143
OpportunityRoot,
42-
OpportunityEvm,
44+
ClientMessage,
45+
BidResponseRoot,
4346
)
44-
from express_relay.svm.generated.express_relay.instructions import submit_bid
47+
from express_relay.models.base import UnsupportedOpportunityVersionException
48+
from express_relay.models.evm import OpportunityEvm
49+
from express_relay.svm.generated.express_relay.instructions.submit_bid import submit_bid
4550
from express_relay.svm.generated.express_relay.program_id import (
4651
PROGRAM_ID as SVM_EXPRESS_RELAY_PROGRAM_ID,
4752
)
48-
from express_relay.svm.generated.express_relay.types import SubmitBidArgs
53+
from express_relay.svm.generated.express_relay.types.submit_bid_args import (
54+
SubmitBidArgs,
55+
)
4956
from express_relay.svm.limo_client import LimoClient
5057

5158

@@ -334,11 +341,10 @@ async def ws_handler(
334341

335342
elif msg_json.get("type") == "bid_status_update":
336343
if bid_status_callback is not None:
337-
bid_status_update = BidStatusUpdate.process_bid_status_dict(
344+
bid_status_update = BidStatusUpdate.model_validate(
338345
msg_json["status"]
339346
)
340-
if bid_status_update:
341-
asyncio.create_task(bid_status_callback(bid_status_update))
347+
asyncio.create_task(bid_status_callback(bid_status_update))
342348

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

370376
opportunities: list[Opportunity] = []
371377
for opportunity in resp.json():
372-
opportunity_processed = OpportunityRoot.model_validate(opportunity)
373-
if opportunity_processed:
378+
try:
379+
opportunity_processed = OpportunityRoot.model_validate(opportunity)
374380
opportunities.append(opportunity_processed.root)
375-
381+
except UnsupportedOpportunityVersionException as e:
382+
warnings.warn(str(e))
376383
return opportunities
377384

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

420427
bids = []
421428
for bid in resp.json()["items"]:
422-
bid_processed = BidResponse.process_bid_response_dict(bid)
423-
if bid_processed:
424-
bids.append(bid_processed)
429+
bid_processed = BidResponseRoot.model_validate(bid)
430+
bids.append(bid_processed.root)
425431

426432
return bids
427433

express_relay/sdk/python/express_relay/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from solders.pubkey import Pubkey
44

5-
from express_relay.express_relay_types import OpportunityAdapterConfig
5+
from express_relay.models import OpportunityAdapterConfig
66

77
OPPORTUNITY_ADAPTER_CONFIGS = {
88
"op_sepolia": OpportunityAdapterConfig(

0 commit comments

Comments
 (0)