Skip to content

Commit a01cb0e

Browse files
m30manihamde
andauthored
feat: limo simple searcher (#1943)
* initial commit of limo example * Update to use the latest sdk * Update apis --------- Co-authored-by: ani <anirudhtx@gmail.com>
1 parent 70b4fac commit a01cb0e

File tree

9 files changed

+5471
-2217
lines changed

9 files changed

+5471
-2217
lines changed

express_relay/sdk/js/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ There is an example searcher in [examples](./src/examples/) directory.
7474

7575
#### SimpleSearcher
7676

77-
[This example](./src/examples/simpleSearcher.ts) fetches `OpportunityParams` from the specified endpoint,
77+
[This example](./src/examples/simpleSearcherEvm.ts) fetches `OpportunityParams` from the specified endpoint,
7878
creates a fixed bid on each opportunity and signs them with the provided private key, and finally submits them back to the server. You can run it with
7979
`npm run simple-searcher`. A full command looks like this:
8080

8181
```bash
82-
npm run simple-searcher -- \
82+
npm run simple-searcher-evm -- \
8383
--endpoint https://per-staging.dourolabs.app/ \
8484
--chain-id op_sepolia \
8585
--private-key <YOUR-PRIVATE-KEY>
@@ -91,8 +91,8 @@ The SimpleSearcherSvm example submits a dummy SVM transaction to the auction ser
9191

9292
```bash
9393
npm run simple-searcher-svm -- \
94-
--endpoint-express-relay http://per-staging.dourolabs.app/ \
95-
--chain-id solana \
94+
--endpoint-express-relay https://per-staging.dourolabs.app/ \
95+
--chain-id development-solana \
9696
--private-key <YOUR-PRIVATE-KEY> \
9797
--endpoint-svm "https://api.mainnet-beta.solana.com"
9898
```

express_relay/sdk/js/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/express-relay-js",
3-
"version": "0.9.1",
3+
"version": "0.10.0",
44
"description": "Utilities for interacting with the express relay protocol",
55
"homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/express_relay/sdk/js",
66
"author": "Douro Labs",
@@ -15,8 +15,9 @@
1515
"scripts": {
1616
"build": "tsc",
1717
"test": "jest src/ --passWithNoTests",
18-
"simple-searcher": "pnpm run build && node lib/examples/simpleSearcher.js",
18+
"simple-searcher-evm": "pnpm run build && node lib/examples/simpleSearcherEvm.js",
1919
"simple-searcher-svm": "pnpm run build && node lib/examples/simpleSearcherSvm.js",
20+
"simple-searcher-limo": "pnpm run build && node lib/examples/simpleSearcherLimo.js",
2021
"generate-api-types": "openapi-typescript http://127.0.0.1:9000/docs/openapi.json --output src/serverTypes.d.ts",
2122
"generate-anchor-types": "anchor idl type src/idl/idlExpressRelay.json --out src/expressRelayTypes.d.ts && anchor idl type src/examples/idl/idlDummy.json --out src/examples/dummyTypes.d.ts",
2223
"format": "prettier --write \"src/**/*.ts\"",
@@ -37,7 +38,9 @@
3738
},
3839
"dependencies": {
3940
"@coral-xyz/anchor": "^0.30.1",
41+
"@kamino-finance/limo-sdk": "^0.2.1",
4042
"@solana/web3.js": "^1.95.3",
43+
"decimal.js": "^10.4.3",
4144
"isomorphic-ws": "^5.0.0",
4245
"openapi-client-axios": "^7.5.5",
4346
"openapi-fetch": "^0.8.2",

express_relay/sdk/js/src/const.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ export const OPPORTUNITY_ADAPTER_CONFIGS: Record<
2626
export const SVM_CONSTANTS: Record<string, SvmConstantsConfig> = {
2727
"development-solana": {
2828
relayerSigner: new PublicKey(
29-
"3pR5W8qPTHKEdoD7mNMx1SwkaPE18aQZwoAKWYpnHozY"
29+
"GEeEguHhepHtPVo3E9RA1wvnxgxJ61iSc9dJfd433w3K"
3030
),
3131
feeReceiverRelayer: new PublicKey(
32-
"3pR5W8qPTHKEdoD7mNMx1SwkaPE18aQZwoAKWYpnHozY"
32+
"feesJcX9zwLiEZs9iQGXeBd65b9m2Zc1LjjyHngQF29"
3333
),
3434
expressRelayProgram: new PublicKey(
35-
"GwEtasTAxdS9neVE4GPUpcwR7DB7AizntQSPcG36ubZM"
35+
"PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou"
3636
),
3737
},
3838
};

express_relay/sdk/js/src/examples/simpleSearcher.ts renamed to express_relay/sdk/js/src/examples/simpleSearcherEvm.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import yargs from "yargs";
22
import { hideBin } from "yargs/helpers";
3-
import { checkHex, Client } from "../index";
3+
import { BidStatusUpdateEvm, checkHex, Client } from "../index";
44
import { privateKeyToAccount } from "viem/accounts";
55
import { isHex } from "viem";
66
import { BidStatusUpdate, Opportunity } from "../types";
77
import { OPPORTUNITY_ADAPTER_CONFIGS } from "../const";
88

99
const DAY_IN_SECONDS = 60 * 60 * 24;
1010

11-
class SimpleSearcher {
11+
class SimpleSearcherEvm {
1212
private client: Client;
1313
constructor(
1414
public endpoint: string,
@@ -27,7 +27,8 @@ class SimpleSearcher {
2727
);
2828
}
2929

30-
async bidStatusHandler(bidStatus: BidStatusUpdate) {
30+
async bidStatusHandler(_bidStatus: BidStatusUpdate) {
31+
const bidStatus = _bidStatus as BidStatusUpdateEvm;
3132
let resultDetails = "";
3233
if (bidStatus.type == "submitted" || bidStatus.type == "won") {
3334
resultDetails = `, transaction ${bidStatus.result}, index ${bidStatus.index} of multicall`;
@@ -40,10 +41,7 @@ class SimpleSearcher {
4041
}
4142
}
4243
console.log(
43-
`Bid status for bid ${bidStatus.id}: ${bidStatus.type.replaceAll(
44-
"_",
45-
" "
46-
)}${resultDetails}`
44+
`Bid status for bid ${bidStatus.id}: ${bidStatus.type}${resultDetails}`
4745
);
4846
}
4947

@@ -127,7 +125,7 @@ async function run() {
127125
} else {
128126
throw new Error(`Invalid private key: ${argv.privateKey}`);
129127
}
130-
const searcher = new SimpleSearcher(
128+
const searcher = new SimpleSearcherEvm(
131129
argv.endpoint,
132130
argv.chainId,
133131
argv.privateKey,
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import yargs from "yargs";
2+
import { hideBin } from "yargs/helpers";
3+
import { Client } from "../index";
4+
import { BidStatusUpdate } from "../types";
5+
import { SVM_CONSTANTS } from "../const";
6+
7+
import * as anchor from "@coral-xyz/anchor";
8+
import { Keypair, PublicKey, Connection } from "@solana/web3.js";
9+
10+
import * as limo from "@kamino-finance/limo-sdk";
11+
import { Decimal } from "decimal.js";
12+
import {
13+
getPdaAuthority,
14+
OrderStateAndAddress,
15+
} from "@kamino-finance/limo-sdk/dist/utils";
16+
17+
const DAY_IN_SECONDS = 60 * 60 * 24;
18+
19+
class SimpleSearcherLimo {
20+
private client: Client;
21+
private connectionSvm: Connection;
22+
private clientLimo: limo.LimoClient;
23+
private searcher: Keypair;
24+
constructor(
25+
public endpointExpressRelay: string,
26+
public chainId: string,
27+
privateKey: string,
28+
public endpointSvm: string,
29+
public globalConfig: PublicKey,
30+
public apiKey?: string
31+
) {
32+
this.client = new Client(
33+
{
34+
baseUrl: endpointExpressRelay,
35+
apiKey,
36+
},
37+
undefined,
38+
() => {
39+
return Promise.resolve();
40+
},
41+
this.bidStatusHandler.bind(this)
42+
);
43+
this.connectionSvm = new Connection(endpointSvm, "confirmed");
44+
this.clientLimo = new limo.LimoClient(this.connectionSvm, globalConfig);
45+
const secretKey = anchor.utils.bytes.bs58.decode(privateKey);
46+
this.searcher = Keypair.fromSecretKey(secretKey);
47+
}
48+
49+
async bidStatusHandler(bidStatus: BidStatusUpdate) {
50+
let resultDetails = "";
51+
if (bidStatus.type == "submitted" || bidStatus.type == "won") {
52+
resultDetails = `, transaction ${bidStatus.result}`;
53+
} else if (bidStatus.type == "lost") {
54+
if (bidStatus.result) {
55+
resultDetails = `, transaction ${bidStatus.result}`;
56+
}
57+
}
58+
console.log(
59+
`Bid status for bid ${bidStatus.id}: ${bidStatus.type}${resultDetails}`
60+
);
61+
}
62+
63+
async evaluateOrder(order: OrderStateAndAddress) {
64+
const inputMintDecimals = await this.clientLimo.getOrderInputMintDecimals(
65+
order
66+
);
67+
const outputMintDecimals = await this.clientLimo.getOrderOutputMintDecimals(
68+
order
69+
);
70+
const inputAmount = new Decimal(
71+
order.state.remainingInputAmount.toNumber()
72+
).div(new Decimal(10).pow(inputMintDecimals));
73+
74+
const outputAmount = new Decimal(
75+
order.state.expectedOutputAmount.toNumber()
76+
).div(new Decimal(10).pow(outputMintDecimals));
77+
78+
console.log("Order address", order.address.toBase58());
79+
console.log(
80+
"Sell token",
81+
order.state.inputMint.toBase58(),
82+
"amount:",
83+
inputAmount.toString()
84+
);
85+
console.log(
86+
"Buy token",
87+
order.state.outputMint.toBase58(),
88+
"amount:",
89+
outputAmount.toString()
90+
);
91+
92+
const ixsTakeOrder = await this.clientLimo.takeOrderIx(
93+
this.searcher.publicKey,
94+
order,
95+
inputAmount,
96+
SVM_CONSTANTS[this.chainId].expressRelayProgram,
97+
inputMintDecimals,
98+
outputMintDecimals
99+
);
100+
const txRaw = new anchor.web3.Transaction().add(...ixsTakeOrder);
101+
102+
const router = getPdaAuthority(
103+
this.clientLimo.getProgramID(),
104+
this.globalConfig
105+
);
106+
const bidAmount = new anchor.BN(argv.bid);
107+
108+
const bid = await this.client.constructSvmBid(
109+
txRaw,
110+
this.searcher.publicKey,
111+
router,
112+
order.address,
113+
bidAmount,
114+
new anchor.BN(Math.round(Date.now() / 1000 + DAY_IN_SECONDS)),
115+
this.chainId
116+
);
117+
118+
try {
119+
const { blockhash } = await this.connectionSvm.getLatestBlockhash();
120+
bid.transaction.recentBlockhash = blockhash;
121+
bid.transaction.sign(this.searcher);
122+
const bidId = await this.client.submitBid(bid);
123+
console.log(`Successful bid. Bid id ${bidId}`);
124+
} catch (error) {
125+
console.error(`Failed to bid: ${error}`);
126+
}
127+
}
128+
129+
async bidOnNewOrders() {
130+
let allOrders =
131+
await this.clientLimo.getAllOrdersStateAndAddressWithFilters([]);
132+
allOrders = allOrders.filter(
133+
(order) => !order.state.remainingInputAmount.isZero()
134+
);
135+
if (allOrders.length === 0) {
136+
console.log("No orders to bid on");
137+
return;
138+
}
139+
for (const order of allOrders) {
140+
await this.evaluateOrder(order);
141+
}
142+
// Note: You need to parallelize this in production with something like:
143+
// await Promise.all(allOrders.map((order) => this.evaluateOrder(order)));
144+
}
145+
146+
async start() {
147+
for (;;) {
148+
await this.bidOnNewOrders();
149+
await new Promise((resolve) => setTimeout(resolve, 2000));
150+
}
151+
}
152+
}
153+
154+
const argv = yargs(hideBin(process.argv))
155+
.option("endpoint-express-relay", {
156+
description:
157+
"Express relay endpoint. e.g: https://per-staging.dourolabs.app/",
158+
type: "string",
159+
demandOption: true,
160+
})
161+
.option("chain-id", {
162+
description: "Chain id to bid on Limo opportunities for. e.g: solana",
163+
type: "string",
164+
demandOption: true,
165+
})
166+
.option("global-config", {
167+
description: "Global config address",
168+
type: "string",
169+
demandOption: true,
170+
})
171+
.option("bid", {
172+
description: "Bid amount in lamports",
173+
type: "string",
174+
default: "100",
175+
})
176+
.option("private-key", {
177+
description: "Private key to sign the bid with. In 64-byte base58 format",
178+
type: "string",
179+
demandOption: true,
180+
})
181+
.option("api-key", {
182+
description:
183+
"The API key of the searcher to authenticate with the server for fetching and submitting bids",
184+
type: "string",
185+
demandOption: false,
186+
})
187+
.option("endpoint-svm", {
188+
description: "SVM RPC endpoint",
189+
type: "string",
190+
demandOption: true,
191+
})
192+
.help()
193+
.alias("help", "h")
194+
.parseSync();
195+
async function run() {
196+
if (!SVM_CONSTANTS[argv.chainId]) {
197+
throw new Error(`SVM constants not found for chain ${argv.chainId}`);
198+
}
199+
const searcherSvm = Keypair.fromSecretKey(
200+
anchor.utils.bytes.bs58.decode(argv.privateKey)
201+
);
202+
console.log(`Using searcher pubkey: ${searcherSvm.publicKey.toBase58()}`);
203+
204+
const simpleSearcher = new SimpleSearcherLimo(
205+
argv.endpointExpressRelay,
206+
argv.chainId,
207+
argv.privateKey,
208+
argv.endpointSvm,
209+
new PublicKey(argv.globalConfig),
210+
argv.apiKey
211+
);
212+
await simpleSearcher.start();
213+
}
214+
215+
run();

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,7 @@ class SimpleSearcherSvm {
5252
}
5353
}
5454
console.log(
55-
`Bid status for bid ${bidStatus.id}: ${bidStatus.type.replaceAll(
56-
"_",
57-
" "
58-
)}${resultDetails}`
55+
`Bid status for bid ${bidStatus.id}: ${bidStatus.type}${resultDetails}`
5956
);
6057
}
6158

0 commit comments

Comments
 (0)