Skip to content

Commit

Permalink
feat: limo simple searcher (#1943)
Browse files Browse the repository at this point in the history
* initial commit of limo example

* Update to use the latest sdk

* Update apis

---------

Co-authored-by: ani <anirudhtx@gmail.com>
  • Loading branch information
m30m and anihamde committed Sep 20, 2024
1 parent 70b4fac commit a01cb0e
Show file tree
Hide file tree
Showing 9 changed files with 5,471 additions and 2,217 deletions.
8 changes: 4 additions & 4 deletions express_relay/sdk/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ There is an example searcher in [examples](./src/examples/) directory.

#### SimpleSearcher

[This example](./src/examples/simpleSearcher.ts) fetches `OpportunityParams` from the specified endpoint,
[This example](./src/examples/simpleSearcherEvm.ts) fetches `OpportunityParams` from the specified endpoint,
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
`npm run simple-searcher`. A full command looks like this:

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

```bash
npm run simple-searcher-svm -- \
--endpoint-express-relay http://per-staging.dourolabs.app/ \
--chain-id solana \
--endpoint-express-relay https://per-staging.dourolabs.app/ \
--chain-id development-solana \
--private-key <YOUR-PRIVATE-KEY> \
--endpoint-svm "https://api.mainnet-beta.solana.com"
```
Expand Down
7 changes: 5 additions & 2 deletions express_relay/sdk/js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pythnetwork/express-relay-js",
"version": "0.9.1",
"version": "0.10.0",
"description": "Utilities for interacting with the express relay protocol",
"homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/express_relay/sdk/js",
"author": "Douro Labs",
Expand All @@ -15,8 +15,9 @@
"scripts": {
"build": "tsc",
"test": "jest src/ --passWithNoTests",
"simple-searcher": "pnpm run build && node lib/examples/simpleSearcher.js",
"simple-searcher-evm": "pnpm run build && node lib/examples/simpleSearcherEvm.js",
"simple-searcher-svm": "pnpm run build && node lib/examples/simpleSearcherSvm.js",
"simple-searcher-limo": "pnpm run build && node lib/examples/simpleSearcherLimo.js",
"generate-api-types": "openapi-typescript http://127.0.0.1:9000/docs/openapi.json --output src/serverTypes.d.ts",
"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",
"format": "prettier --write \"src/**/*.ts\"",
Expand All @@ -37,7 +38,9 @@
},
"dependencies": {
"@coral-xyz/anchor": "^0.30.1",
"@kamino-finance/limo-sdk": "^0.2.1",
"@solana/web3.js": "^1.95.3",
"decimal.js": "^10.4.3",
"isomorphic-ws": "^5.0.0",
"openapi-client-axios": "^7.5.5",
"openapi-fetch": "^0.8.2",
Expand Down
6 changes: 3 additions & 3 deletions express_relay/sdk/js/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ export const OPPORTUNITY_ADAPTER_CONFIGS: Record<
export const SVM_CONSTANTS: Record<string, SvmConstantsConfig> = {
"development-solana": {
relayerSigner: new PublicKey(
"3pR5W8qPTHKEdoD7mNMx1SwkaPE18aQZwoAKWYpnHozY"
"GEeEguHhepHtPVo3E9RA1wvnxgxJ61iSc9dJfd433w3K"
),
feeReceiverRelayer: new PublicKey(
"3pR5W8qPTHKEdoD7mNMx1SwkaPE18aQZwoAKWYpnHozY"
"feesJcX9zwLiEZs9iQGXeBd65b9m2Zc1LjjyHngQF29"
),
expressRelayProgram: new PublicKey(
"GwEtasTAxdS9neVE4GPUpcwR7DB7AizntQSPcG36ubZM"
"PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou"
),
},
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { checkHex, Client } from "../index";
import { BidStatusUpdateEvm, checkHex, Client } from "../index";
import { privateKeyToAccount } from "viem/accounts";
import { isHex } from "viem";
import { BidStatusUpdate, Opportunity } from "../types";
import { OPPORTUNITY_ADAPTER_CONFIGS } from "../const";

const DAY_IN_SECONDS = 60 * 60 * 24;

class SimpleSearcher {
class SimpleSearcherEvm {
private client: Client;
constructor(
public endpoint: string,
Expand All @@ -27,7 +27,8 @@ class SimpleSearcher {
);
}

async bidStatusHandler(bidStatus: BidStatusUpdate) {
async bidStatusHandler(_bidStatus: BidStatusUpdate) {
const bidStatus = _bidStatus as BidStatusUpdateEvm;
let resultDetails = "";
if (bidStatus.type == "submitted" || bidStatus.type == "won") {
resultDetails = `, transaction ${bidStatus.result}, index ${bidStatus.index} of multicall`;
Expand All @@ -40,10 +41,7 @@ class SimpleSearcher {
}
}
console.log(
`Bid status for bid ${bidStatus.id}: ${bidStatus.type.replaceAll(
"_",
" "
)}${resultDetails}`
`Bid status for bid ${bidStatus.id}: ${bidStatus.type}${resultDetails}`
);
}

Expand Down Expand Up @@ -127,7 +125,7 @@ async function run() {
} else {
throw new Error(`Invalid private key: ${argv.privateKey}`);
}
const searcher = new SimpleSearcher(
const searcher = new SimpleSearcherEvm(
argv.endpoint,
argv.chainId,
argv.privateKey,
Expand Down
215 changes: 215 additions & 0 deletions express_relay/sdk/js/src/examples/simpleSearcherLimo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { Client } from "../index";
import { BidStatusUpdate } from "../types";
import { SVM_CONSTANTS } from "../const";

import * as anchor from "@coral-xyz/anchor";
import { Keypair, PublicKey, Connection } from "@solana/web3.js";

import * as limo from "@kamino-finance/limo-sdk";
import { Decimal } from "decimal.js";
import {
getPdaAuthority,
OrderStateAndAddress,
} 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 searcher: Keypair;
constructor(
public endpointExpressRelay: string,
public chainId: string,
privateKey: string,
public endpointSvm: string,
public globalConfig: PublicKey,
public apiKey?: string
) {
this.client = new Client(
{
baseUrl: endpointExpressRelay,
apiKey,
},
undefined,
() => {
return Promise.resolve();
},
this.bidStatusHandler.bind(this)
);
this.connectionSvm = new Connection(endpointSvm, "confirmed");
this.clientLimo = new limo.LimoClient(this.connectionSvm, globalConfig);
const secretKey = anchor.utils.bytes.bs58.decode(privateKey);
this.searcher = Keypair.fromSecretKey(secretKey);
}

async bidStatusHandler(bidStatus: BidStatusUpdate) {
let resultDetails = "";
if (bidStatus.type == "submitted" || bidStatus.type == "won") {
resultDetails = `, transaction ${bidStatus.result}`;
} else if (bidStatus.type == "lost") {
if (bidStatus.result) {
resultDetails = `, transaction ${bidStatus.result}`;
}
}
console.log(
`Bid status for bid ${bidStatus.id}: ${bidStatus.type}${resultDetails}`
);
}

async evaluateOrder(order: OrderStateAndAddress) {
const inputMintDecimals = await this.clientLimo.getOrderInputMintDecimals(
order
);
const outputMintDecimals = await this.clientLimo.getOrderOutputMintDecimals(
order
);
const inputAmount = new Decimal(
order.state.remainingInputAmount.toNumber()
).div(new Decimal(10).pow(inputMintDecimals));

const outputAmount = new Decimal(
order.state.expectedOutputAmount.toNumber()
).div(new Decimal(10).pow(outputMintDecimals));

console.log("Order address", order.address.toBase58());
console.log(
"Sell token",
order.state.inputMint.toBase58(),
"amount:",
inputAmount.toString()
);
console.log(
"Buy token",
order.state.outputMint.toBase58(),
"amount:",
outputAmount.toString()
);

const ixsTakeOrder = await this.clientLimo.takeOrderIx(
this.searcher.publicKey,
order,
inputAmount,
SVM_CONSTANTS[this.chainId].expressRelayProgram,
inputMintDecimals,
outputMintDecimals
);
const txRaw = new anchor.web3.Transaction().add(...ixsTakeOrder);

const router = getPdaAuthority(
this.clientLimo.getProgramID(),
this.globalConfig
);
const bidAmount = new anchor.BN(argv.bid);

const bid = await this.client.constructSvmBid(
txRaw,
this.searcher.publicKey,
router,
order.address,
bidAmount,
new anchor.BN(Math.round(Date.now() / 1000 + DAY_IN_SECONDS)),
this.chainId
);

try {
const { blockhash } = await this.connectionSvm.getLatestBlockhash();
bid.transaction.recentBlockhash = blockhash;
bid.transaction.sign(this.searcher);
const bidId = await this.client.submitBid(bid);
console.log(`Successful bid. Bid id ${bidId}`);
} catch (error) {
console.error(`Failed to bid: ${error}`);
}
}

async bidOnNewOrders() {
let allOrders =
await this.clientLimo.getAllOrdersStateAndAddressWithFilters([]);
allOrders = allOrders.filter(
(order) => !order.state.remainingInputAmount.isZero()
);
if (allOrders.length === 0) {
console.log("No orders to bid on");
return;
}
for (const order of allOrders) {
await this.evaluateOrder(order);
}
// Note: You need to parallelize this in production with something like:
// await Promise.all(allOrders.map((order) => this.evaluateOrder(order)));
}

async start() {
for (;;) {
await this.bidOnNewOrders();
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
}

const argv = yargs(hideBin(process.argv))
.option("endpoint-express-relay", {
description:
"Express relay endpoint. e.g: https://per-staging.dourolabs.app/",
type: "string",
demandOption: true,
})
.option("chain-id", {
description: "Chain id to bid on Limo opportunities for. e.g: solana",
type: "string",
demandOption: true,
})
.option("global-config", {
description: "Global config address",
type: "string",
demandOption: true,
})
.option("bid", {
description: "Bid amount in lamports",
type: "string",
default: "100",
})
.option("private-key", {
description: "Private key to sign the bid with. In 64-byte base58 format",
type: "string",
demandOption: true,
})
.option("api-key", {
description:
"The API key of the searcher to authenticate with the server for fetching and submitting bids",
type: "string",
demandOption: false,
})
.option("endpoint-svm", {
description: "SVM RPC endpoint",
type: "string",
demandOption: true,
})
.help()
.alias("help", "h")
.parseSync();
async function run() {
if (!SVM_CONSTANTS[argv.chainId]) {
throw new Error(`SVM constants not found for chain ${argv.chainId}`);
}
const searcherSvm = Keypair.fromSecretKey(
anchor.utils.bytes.bs58.decode(argv.privateKey)
);
console.log(`Using searcher pubkey: ${searcherSvm.publicKey.toBase58()}`);

const simpleSearcher = new SimpleSearcherLimo(
argv.endpointExpressRelay,
argv.chainId,
argv.privateKey,
argv.endpointSvm,
new PublicKey(argv.globalConfig),
argv.apiKey
);
await simpleSearcher.start();
}

run();
5 changes: 1 addition & 4 deletions express_relay/sdk/js/src/examples/simpleSearcherSvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ class SimpleSearcherSvm {
}
}
console.log(
`Bid status for bid ${bidStatus.id}: ${bidStatus.type.replaceAll(
"_",
" "
)}${resultDetails}`
`Bid status for bid ${bidStatus.id}: ${bidStatus.type}${resultDetails}`
);
}

Expand Down
Loading

0 comments on commit a01cb0e

Please sign in to comment.