Skip to content

Commit

Permalink
api: update jupiter tx (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
yurushao authored Jun 10, 2024
1 parent f9c4483 commit 6686f39
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 89 deletions.
7 changes: 2 additions & 5 deletions anchor/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { GlamClientConfig } from "./clientConfig";
import {
BaseClient,
JUPITER_API_DEFAULT as _JUPITER_API_DEFAULT
} from "./client/base";
import { BaseClient } from "./client/base";
import { DriftClient } from "./client/drift";
import { InvestorClient } from "./client/investor";
import { JupiterClient } from "./client/jupiter";
import { MarinadeClient } from "./client/marinade";
import { WSolClient } from "./client/wsol";

export const JUPITER_API_DEFAULT = _JUPITER_API_DEFAULT;
export { JUPITER_API_DEFAULT } from "./client/base";

export class GlamClient extends BaseClient {
drift: DriftClient;
Expand Down
43 changes: 32 additions & 11 deletions anchor/src/client/jupiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const jupiterProgram = new PublicKey(
);

interface QuoteParams {
inputMint: PublicKey;
outputMint: PublicKey;
inputMint: string;
outputMint: string;
amount: number;
autoSlippage?: boolean;
autoSlippageCollisionUsdValue?: number;
Expand Down Expand Up @@ -57,13 +57,16 @@ export class JupiterClient {
swapInstruction?: any,
addressLookupTableAddresses?: any
): Promise<TransactionSignature> {
const outputMint =
quoteParams?.outputMint || new PublicKey(quoteResponse!.outputMint);

// TODO: should not allow client side to specify destination token account
const outputMint = quoteParams?.outputMint || quoteResponse!.outputMint;
const destinationTokenAccount = this.base.getTreasuryAta(
fund,
new PublicKey(outputMint)
);
const tx = await this.swapTxBuilder(
fund,
this.base.getManager(),
this.base.getTreasuryAta(fund, outputMint),
destinationTokenAccount,
quoteParams,
quoteResponse,
swapInstruction,
Expand Down Expand Up @@ -178,7 +181,7 @@ export class JupiterClient {
.accounts({
fund,
manager,
inputAta: this.base.getManagerAta(inputMint),
inputAta: this.base.getManagerAta(new PublicKey(inputMint)),
treasury: this.base.getTreasuryPDA(fund),
outputAta: destinationTokenAccount,
inputMint,
Expand All @@ -191,8 +194,18 @@ export class JupiterClient {
const addressLookupTableAccounts = await this.getAdressLookupTableAccounts(
addressLookupTableAddresses
);

let payerPublicKey;
try {
payerPublicKey = await this.base.getWalletSigner().publicKey;
} catch (e) {
console.log("Cannot get wallet signer:", e);
console.log("Default to fund manager as payer");
payerPublicKey = manager;
}

const messageV0 = new TransactionMessage({
payerKey: this.base.getWalletSigner().publicKey,
payerKey: payerPublicKey,
recentBlockhash: (
await this.base.provider.connection.getLatestBlockhash()
).blockhash,
Expand Down Expand Up @@ -242,15 +255,23 @@ export class JupiterClient {
public async swapTx(
fund: PublicKey,
manager: PublicKey,
quote?: any,
quoteResponse?: any,
quoteParams?: QuoteParams,
quoteResponse?: QuoteResponse,
swapInstruction?: any,
addressLookupTableAddresses?: any
): Promise<VersionedTransaction> {
// TODO: should not allow client side to specify destination token account
const outputMint = quoteParams?.outputMint || quoteResponse!.outputMint;
const destinationTokenAccount = this.base.getTreasuryAta(
fund,
new PublicKey(outputMint)
);

return await this.swapTxBuilder(
fund,
manager,
quote,
destinationTokenAccount,
quoteParams,
quoteResponse,
swapInstruction,
addressLookupTableAddresses
Expand Down
8 changes: 4 additions & 4 deletions anchor/tests/glam_jupiter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ describe("glam_jupiter", () => {
const amount = 50_000_000;
try {
const txId = await glamClient.jupiter.swap(fundPDA, {
inputMint: usdc,
outputMint: msol,
inputMint: usdc.toBase58(),
outputMint: msol.toBase58(),
amount,
autoSlippage: true,
autoSlippageCollisionUsdValue: 1000,
Expand All @@ -56,8 +56,8 @@ describe("glam_jupiter", () => {
const amount = 50_000_000;
try {
const txId = await glamClient.jupiter.swap(fundPDA, {
inputMint: wsol,
outputMint: msol,
inputMint: wsol.toBase58(),
outputMint: msol.toBase58(),
amount,
autoSlippage: true,
autoSlippageCollisionUsdValue: 1000,
Expand Down
3 changes: 3 additions & 0 deletions api/.env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# default RPC is localhost:8899
SOLANA_RPC=https://api.mainnet-beta.solana.com
SOLANA_CLUSTER=mainnet-beta
3 changes: 3 additions & 0 deletions api/.env.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
env_variables:
SOLANA_RPC: https://api.mainnet-beta.solana.com
SOLANA_CLUSTER: mainnet-beta
43 changes: 20 additions & 23 deletions api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,31 @@ if (process.env.NODE_ENV !== "production") {
require("dotenv").config({ path: ".env.local", override: true });
}

const BASE_URL = "https://api.glam.systems";
const SOLANA_RPC = process.env.SOLANA_RPC || "http://localhost:8899";
const SOLANA_MAINNET_KEY = process.env.SOLANA_MAINNET_KEY || "";
const FORCE_MAINNET = process.env.MAINNET === "1";
const SOLANA_CLUSTER = (process.env.SOLANA_CLUSTER || "custom") as any;
const JUPITER_API = process.env.JUPITER_API || JUPITER_API_DEFAULT;

/* GlamClient for devnet and testnet */
/* GlamClient setup */

const mainnetConnection = new Connection(
`https://mainnet.helius-rpc.com/?api-key=${SOLANA_MAINNET_KEY}`,
"confirmed"
);
const mainnetProvider = new AnchorProvider(mainnetConnection, null, {
commitment: "confirmed"
});
const mainnetClient = new GlamClient({
cluster: "mainnet-beta",
provider: mainnetProvider,
// Default client is configured by environment variables
const defaultClient = new GlamClient({
cluster: SOLANA_CLUSTER,
provider: new AnchorProvider(
new Connection(SOLANA_RPC, "confirmed"),
null,
{}
),
jupiterApi: JUPITER_API
});

const devnetConnection = new Connection(SOLANA_RPC, "confirmed");
const devnetProvider = new AnchorProvider(devnetConnection, null, {
commitment: "confirmed"
const devnetClient = new GlamClient({
cluster: "devnet",
provider: new AnchorProvider(
new Connection("https://api.devnet.solana.com", "confirmed"),
null,
{}
)
});
const devnetClient = FORCE_MAINNET
? mainnetClient
: new GlamClient({ provider: devnetProvider });

/* Express app */

Expand All @@ -52,9 +49,9 @@ app.use(cors({ origin: "*", methods: "GET" }));
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, "assets")));
app.use((req, res, next) => {
if (req.hostname === "api.glam.systems") {
req.client = mainnetClient;
} else {
req.client = defaultClient;
// Use devnet client if running on GAE and not on api.glam.systems
if (process.env.GAE_SERVICE && req.hostname !== "api.glam.systems") {
req.client = devnetClient;
}
next();
Expand Down
88 changes: 42 additions & 46 deletions api/src/routers/tx.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Router } from "express";
import { validatePubkey, validateBN } from "../validation";
import { Transaction, VersionedTransaction } from "@solana/web3.js";

/*
* Marinade
Expand All @@ -13,20 +14,19 @@ const jupiterSwapTx = async (client, req, res) => {
return res.sendStatus(400);
}

let tx;
try {
tx = await client.jupiter.swapTx(
const tx = await client.jupiter.swapTx(
fund,
manager,
req.body.quote,
req.body.quoteResponse,
req.body.swapInstruction
);
return serializeVersionedTx(tx, res);
} catch (err) {
console.log(err);
return res.status(400).send({ error: err.message });
}
return await serializeTx(tx, manager, client, res);
};

/*
Expand Down Expand Up @@ -60,66 +60,41 @@ const marinadeDelayedUnstakeClaimTx = async (client, req, res) => {
return await serializeTx(tx, manager, client, res);
};

/*
* wSOL
*/

const wsolWrapTx = async (client, req, res) => {
const fund = validatePubkey(req.body.fund);
const manager = validatePubkey(req.body.manager);
const amount = validateBN(req.body.amount);

if (fund === undefined || manager === undefined || amount === undefined) {
return res.sendStatus(400);
}

console.log("client", client);
const tx = await client.wsol.wrapTx(fund, manager, amount);

return await serializeTx(tx, manager, client, res);
};

const wsolUnwrapTx = async (client, req, res) => {
const fund = validatePubkey(req.body.fund);
const manager = validatePubkey(req.body.manager);

if (fund === undefined || manager === undefined) {
return res.sendStatus(400);
}

const tx = await client.wsol.unwrapTx(fund, manager);

return await serializeTx(tx, manager, client, res);
};

/*
* Common
*/

const serializeTx = async (tx, manager, client, res) => {
const serializeTx = async (tx: Transaction, manager, client, res) => {
tx.feePayer = manager;

let serializedTx = "";
try {
tx.recentBlockhash = (
await client.provider.connection.getLatestBlockhash()
).blockhash;

serializedTx = new Buffer(
const serializedTx = Buffer.from(
tx.serialize({
requireAllSignatures: false,
verifySignatures: false
})
).toString("hex");
return res.send({ tx: serializedTx, versioned: false });
} catch (err) {
console.log(err);
return res.status(400).send({ error: err.message });
}
};

const serializeVersionedTx = async (tx: VersionedTransaction, res) => {
try {
const serializedTx = Buffer.from(tx.serialize()).toString("hex");
return res.send({
tx: serializedTx,
versioned: true
});
} catch (err) {
console.log(err);
return res.status(400).send({ error: err.message });
}
return res.send(
JSON.stringify({
tx: serializedTx
}) + "\n"
);
};

/*
Expand All @@ -144,13 +119,34 @@ router.post("/tx/marinade/unstake/claim", async (req, res) => {
});

router.post("/tx/wsol/wrap", async (req, res) => {
const fund = validatePubkey(req.body.fund);
const manager = validatePubkey(req.body.manager);
const amount = validateBN(req.body.amount);

console.log(fund, manager, amount);

if (!fund || !manager || !amount) {
return res.sendStatus(400);
}

res.set("content-type", "application/json");
return wsolWrapTx(req.client, req, res);
const tx = await req.client.wsol.wrapTx(fund, manager, amount);

return await serializeTx(tx, manager, req.client, res);
});

router.post("/tx/wsol/unwrap", async (req, res) => {
const fund = validatePubkey(req.body.fund);
const manager = validatePubkey(req.body.manager);

if (!fund || !manager) {
return res.sendStatus(400);
}

res.set("content-type", "application/json");
return wsolUnwrapTx(req.client, req, res);
const tx = await req.client.wsol.unwrapTx(fund, manager);

return await serializeTx(tx, manager, req.client, res);
});

export default router;
1 change: 1 addition & 0 deletions api/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const validateBN = (num: string) => {
try {
res = new BN(num);
} catch (_e) {
console.error(_e);
return undefined;
}
return res;
Expand Down

0 comments on commit 6686f39

Please sign in to comment.