Add error handling, custom metrics, fix anchorprovider
This commit is contained in:
parent
df9b81be1a
commit
bea1465790
146
src/index.ts
146
src/index.ts
|
@ -16,15 +16,18 @@ import {
|
||||||
} from "@orca-so/whirlpools-sdk";
|
} from "@orca-so/whirlpools-sdk";
|
||||||
import {
|
import {
|
||||||
AnchorProvider,
|
AnchorProvider,
|
||||||
|
Wallet,
|
||||||
BN,
|
BN,
|
||||||
BorshAccountsCoder,
|
BorshAccountsCoder,
|
||||||
Idl,
|
Idl,
|
||||||
} from "@project-serum/anchor";
|
} from "@project-serum/anchor";
|
||||||
import {
|
import {
|
||||||
|
Connection,
|
||||||
Cluster,
|
Cluster,
|
||||||
clusterApiUrl,
|
clusterApiUrl,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
TransactionInstruction,
|
TransactionInstruction,
|
||||||
|
Keypair,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
@ -42,13 +45,25 @@ collectDefaultMetrics({
|
||||||
});
|
});
|
||||||
|
|
||||||
import promBundle from "express-prom-bundle";
|
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 cluster = (CLUSTER || "mainnet-beta") as Cluster;
|
||||||
const maxRoutes = parseInt(MAX_ROUTES || "2");
|
const maxRoutes = parseInt(MAX_ROUTES || "2");
|
||||||
const port = parseInt(PORT || "5000");
|
const port = parseInt(PORT || "5000");
|
||||||
const rpcUrl = RPC_URL || clusterApiUrl(cluster);
|
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 {
|
enum SwapMode {
|
||||||
ExactIn = "ExactIn",
|
ExactIn = "ExactIn",
|
||||||
ExactOut = "ExactOut",
|
ExactOut = "ExactOut",
|
||||||
|
@ -345,7 +360,12 @@ class Router {
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// init anchor, mango & orca
|
// 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(
|
const mangoClient = await MangoClient.connect(
|
||||||
anchorProvider,
|
anchorProvider,
|
||||||
cluster,
|
cluster,
|
||||||
|
@ -365,65 +385,77 @@ async function main() {
|
||||||
app.use(promMetrics);
|
app.use(promMetrics);
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.get("/swap", async (req, res) => {
|
app.get("/swap", async (req, res) => {
|
||||||
const wallet = new PublicKey(req.query.wallet as string);
|
try {
|
||||||
const inputMint = new PublicKey(req.query.inputMint as string);
|
const wallet = new PublicKey(req.query.wallet as string);
|
||||||
const outputMint = new PublicKey(req.query.outputMint as string);
|
const inputMint = new PublicKey(req.query.inputMint as string);
|
||||||
const mode = req.query.mode as SwapMode;
|
const outputMint = new PublicKey(req.query.outputMint as string);
|
||||||
const slippage = Number(req.query.slippage as string);
|
const mode = req.query.mode as SwapMode;
|
||||||
const amount = new BN(req.query.amount as string);
|
const slippage = Number(req.query.slippage as string);
|
||||||
const otherAmountThreshold = req.query.otherAmountThreshold
|
const amount = new BN(req.query.amount as string);
|
||||||
? new BN(req.query.otherAmountThreshold as string)
|
const otherAmountThreshold = req.query.otherAmountThreshold
|
||||||
: mode == SwapMode.ExactIn
|
? new BN(req.query.otherAmountThreshold as string)
|
||||||
? ZERO
|
: mode == SwapMode.ExactIn
|
||||||
: U64_MAX;
|
? ZERO
|
||||||
|
: U64_MAX;
|
||||||
|
|
||||||
if (mode !== SwapMode.ExactIn && mode !== SwapMode.ExactOut) {
|
if (mode !== SwapMode.ExactIn && mode !== SwapMode.ExactOut) {
|
||||||
const error = { e: "mode needs to be one of ExactIn or ExactOut" };
|
const error = { e: "mode needs to be one of ExactIn or ExactOut" };
|
||||||
res.status(404).send(error);
|
res.status(404).send(error);
|
||||||
return;
|
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);
|
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
|
// 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
|
// TEST2: http://localhost:5000/swap?wallet=Bz9thGbRRfwq3EFtFtSKZYnnXio5LXDaRgJDh3NrMAGT&inputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&outputMint=So11111111111111111111111111111111111111112&mode=ExactOut&amount=7000000000&otherAmountThreshold=100000000&slippage=0.001
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue