Add error handling, custom metrics, fix anchorprovider

This commit is contained in:
Riordan Panayides 2023-01-05 19:31:31 +00:00
parent df9b81be1a
commit bea1465790
1 changed files with 89 additions and 57 deletions

View File

@ -16,15 +16,18 @@ import {
} from "@orca-so/whirlpools-sdk";
import {
AnchorProvider,
Wallet,
BN,
BorshAccountsCoder,
Idl,
} from "@project-serum/anchor";
import {
Connection,
Cluster,
clusterApiUrl,
PublicKey,
TransactionInstruction,
Keypair,
} from "@solana/web3.js";
import cors from "cors";
import express from "express";
@ -42,13 +45,25 @@ collectDefaultMetrics({
});
import promBundle from "express-prom-bundle";
const promMetrics = promBundle({ includeMethod: true });
const metricsApp = express();
const promMetrics = promBundle({
includeMethod: true,
metricsApp,
autoregister: false,
});
const cluster = (CLUSTER || "mainnet-beta") as Cluster;
const maxRoutes = parseInt(MAX_ROUTES || "2");
const port = parseInt(PORT || "5000");
const rpcUrl = RPC_URL || clusterApiUrl(cluster);
const metricSwapDuration = new prom.Histogram({
name: "swap_processing_duration",
help: "Swap processing duration",
buckets: [10, 20, 30, 40],
});
prom.register.registerMetric(metricSwapDuration);
enum SwapMode {
ExactIn = "ExactIn",
ExactOut = "ExactOut",
@ -345,7 +360,12 @@ class Router {
async function main() {
// init anchor, mango & orca
const anchorProvider = AnchorProvider.local(rpcUrl);
const connection = new Connection(rpcUrl, "confirmed");
const anchorProvider = new AnchorProvider(
connection,
new Wallet(Keypair.generate()),
{}
);
const mangoClient = await MangoClient.connect(
anchorProvider,
cluster,
@ -365,65 +385,77 @@ async function main() {
app.use(promMetrics);
app.use(cors());
app.get("/swap", async (req, res) => {
const wallet = new PublicKey(req.query.wallet as string);
const inputMint = new PublicKey(req.query.inputMint as string);
const outputMint = new PublicKey(req.query.outputMint as string);
const mode = req.query.mode as SwapMode;
const slippage = Number(req.query.slippage as string);
const amount = new BN(req.query.amount as string);
const otherAmountThreshold = req.query.otherAmountThreshold
? new BN(req.query.otherAmountThreshold as string)
: mode == SwapMode.ExactIn
? ZERO
: U64_MAX;
try {
const wallet = new PublicKey(req.query.wallet as string);
const inputMint = new PublicKey(req.query.inputMint as string);
const outputMint = new PublicKey(req.query.outputMint as string);
const mode = req.query.mode as SwapMode;
const slippage = Number(req.query.slippage as string);
const amount = new BN(req.query.amount as string);
const otherAmountThreshold = req.query.otherAmountThreshold
? new BN(req.query.otherAmountThreshold as string)
: mode == SwapMode.ExactIn
? ZERO
: U64_MAX;
if (mode !== SwapMode.ExactIn && mode !== SwapMode.ExactOut) {
const error = { e: "mode needs to be one of ExactIn or ExactOut" };
res.status(404).send(error);
return;
if (mode !== SwapMode.ExactIn && mode !== SwapMode.ExactOut) {
const error = { e: "mode needs to be one of ExactIn or ExactOut" };
res.status(404).send(error);
return;
}
const timerSwapDuration = metricSwapDuration.startTimer();
const results = await router.swap(
inputMint,
outputMint,
amount,
otherAmountThreshold,
mode,
slippage
);
const swapDuration = timerSwapDuration();
metricSwapDuration.observe(swapDuration);
console.log("swap", swapDuration);
const filtered = results.filter((r) => r.ok);
let ranked: SwapResult[] = [];
if (mode === SwapMode.ExactIn) {
ranked = filtered.sort((a, b) =>
b.minAmtOut.sub(a.minAmtOut).toNumber()
);
} else if (mode === SwapMode.ExactOut) {
ranked = filtered.sort((a, b) => a.maxAmtIn.sub(b.maxAmtIn).toNumber());
}
const topN = ranked.slice(0, Math.min(ranked.length, maxRoutes));
const response = await Promise.all(
topN.map(async (r) => {
const instructions = await r.instructions(wallet);
return {
amount: amount.toString(),
otherAmountThreshold: otherAmountThreshold.toString(),
swapMode: mode,
slippageBps: Math.round(slippage * 10000),
inAmount: r.maxAmtIn.toString(),
outAmount: r.minAmtOut.toString(),
mints: Array.from(new Set(r.mints.map((m) => m.toString()))),
instructions: instructions.map((i) => ({
keys: i.keys.map((k) => ({ ...k, pubkey: k.pubkey.toString() })),
programId: i.programId.toString(),
data: i.data.toString("base64"),
})),
};
})
);
res.send(response);
} catch (err) {
console.error(err);
res.status(500).send();
}
const results = await router.swap(
inputMint,
outputMint,
amount,
otherAmountThreshold,
mode,
slippage
);
const filtered = results.filter((r) => r.ok);
let ranked: SwapResult[] = [];
if (mode === SwapMode.ExactIn) {
ranked = filtered.sort((a, b) => b.minAmtOut.sub(a.minAmtOut).toNumber());
} else if (mode === SwapMode.ExactOut) {
ranked = filtered.sort((a, b) => a.maxAmtIn.sub(b.maxAmtIn).toNumber());
}
const topN = ranked.slice(0, Math.min(ranked.length, maxRoutes));
const response = await Promise.all(
topN.map(async (r) => {
const instructions = await r.instructions(wallet);
return {
amount: amount.toString(),
otherAmountThreshold: otherAmountThreshold.toString(),
swapMode: mode,
slippageBps: Math.round(slippage * 10000),
inAmount: r.maxAmtIn.toString(),
outAmount: r.minAmtOut.toString(),
mints: Array.from(new Set(r.mints.map((m) => m.toString()))),
instructions: instructions.map((i) => ({
keys: i.keys.map((k) => ({ ...k, pubkey: k.pubkey.toString() })),
programId: i.programId.toString(),
data: i.data.toString("base64"),
})),
};
})
);
res.send(response);
});
app.listen(port);
metricsApp.listen(9091);
// TEST1: http://localhost:5000/swap?wallet=Bz9thGbRRfwq3EFtFtSKZYnnXio5LXDaRgJDh3NrMAGT&inputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&outputMint=So11111111111111111111111111111111111111112&mode=ExactIn&amount=100000000&otherAmountThreshold=7000000000&slippage=0.001
// TEST2: http://localhost:5000/swap?wallet=Bz9thGbRRfwq3EFtFtSKZYnnXio5LXDaRgJDh3NrMAGT&inputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&outputMint=So11111111111111111111111111111111111111112&mode=ExactOut&amount=7000000000&otherAmountThreshold=100000000&slippage=0.001
}