Skip to content

Instantly share code, notes, and snippets.

@tubackkhoa
Created December 5, 2025 04:24
Show Gist options
  • Select an option

  • Save tubackkhoa/2fb853ee9533cecd80615c6fada15b3e to your computer and use it in GitHub Desktop.

Select an option

Save tubackkhoa/2fb853ee9533cecd80615c6fada15b3e to your computer and use it in GitHub Desktop.
import "dotenv/config";
import { VersionedTransaction, Keypair } from "@solana/web3.js";
import nacl from "tweetnacl";
import { z } from "zod";
import bs58 from "bs58";
export const TriggerPriceOrderSchema = z.object({
id: z.string(),
user: z.string(),
inToken: z.string(),
outToken: z.string(),
inAmount: z.string(),
triggerPrice: z.string(),
outAmountMin: z.string().optional(),
status: z.enum(["open", "filled", "cancelled", "failed"]),
createdAt: z.number(),
updatedAt: z.number(),
// ... more fields
});
export const TriggerOrderSchema = z.object({
/* DCA/TWAP fields */
});
export const TriggerPriceOrderHistorySchema = z.object({
/* history fields */
});
const LITE_API_BASE =
process.env.NEXT_PUBLIC_JUPITER_TRIGGER_API_URL ?? "https://lite-api.jup.ag/trigger/v1";
const TRIGGER_API_BASE =
typeof window !== "undefined"
? (localStorage.getItem("TRIGGER_API_URL") ?? "https://trigger.jup.ag")
: "https://trigger.jup.ag";
/**
* Jupiter Limit Orderbook V2 SDK
* Supports Trigger Orders (DCA/TWAP) + Advanced Limit Orders (Price Triggers, OCO, OTOCO)
*/
export class JupiterLimitV2 {
private static cookies: string;
public static setToken(token: string) {
this.cookies = ["privy-token=" + token, "privy-session=privy.jup.ag"].join("; ");
}
private static getHeaders(includeCredentials = true): RequestInit {
return {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Cookie: this.cookies, // This is the magic
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
},
credentials: includeCredentials ? "include" : "omit",
};
}
// === Lite API (Public Trigger Orders - DCA/TWAP) ===
static async getTriggerOrders(
params: Record<string, string | number | boolean>,
options?: RequestInit,
) {
const url = new URL(`${LITE_API_BASE}/getTriggerOrders`);
url.search = new URLSearchParams(params as Record<string, string>).toString();
return fetch(url, { ...this.getHeaders(), ...options })
.then((res) => res.json())
.then((data) => z.array(TriggerOrderSchema).parse(data));
}
static async createOrder(body: any, options?: RequestInit) {
return fetch(`${LITE_API_BASE}/createOrder`, {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async cancelOrders(body: { orderIds: string[] }, options?: RequestInit) {
return fetch(`${LITE_API_BASE}/cancelOrders`, {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async authenticatePrivy(wallet: Keypair) {
const address = wallet.publicKey.toBase58();
const { nonce } = await fetch("https://privy.jup.ag/api/v1/siws/init", {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"privy-app-id": "cm8pkejey0052l1ljvgxjztau",
"privy-ca-id": "7f61aba2-152f-4e8d-a1cb-0398d30a3c60",
"privy-client": "react-auth:2.25.0",
Origin: "https://jup.ag/",
},
body: `{"address":"${address}"}`,
method: "POST",
credentials: "include",
}).then((res) => res.json());
console.log(nonce);
const message = `jup.ag wants you to sign in with your Solana account:\n${address}\n\nYou are proving you own ${address}.\n\nURI: https://jup.ag\nVersion: 1\nChain ID: mainnet\nNonce: ${nonce}\nIssued At: 2025-12-05T03:56:44.926Z\nResources:\n- https://privy.io`;
// MUST convert to bytes first
const messageBytes = new TextEncoder().encode(message);
// Sign using ed25519
const signature = nacl.sign.detached(messageBytes, wallet.secretKey);
// Convert to base64 for Privy
const signatureBase64 = Buffer.from(signature).toString("base64");
return fetch("https://privy.jup.ag/api/v1/siws/authenticate", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"privy-app-id": "cm8pkejey0052l1ljvgxjztau",
Origin: "https://jup.ag/",
},
body: JSON.stringify({
message,
signature: signatureBase64,
walletClientType: "jupiter",
connectorType: "solana_adapter",
mode: "login-or-sign-up",
message_type: "plain",
}),
}).then(async (res) => {
const data = await res.json();
console.log(data);
// When successful, store the token automatically
if (data?.token) JupiterLimitV2.setToken(data.token);
return data;
});
}
static async execute(body: any, options?: RequestInit) {
return fetch(`${LITE_API_BASE}/execute`, {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async getFeeStructure(options?: RequestInit) {
return fetch(`${LITE_API_BASE}/getFees`, {
...this.getHeaders(false),
...options,
}).then((res) => res.json());
}
// === Trigger API (Advanced Limit Orders - Price Triggers) ===
private static triggerUrl(path: string) {
return `${TRIGGER_API_BASE}${path}`;
}
// Privy Wallet (optional auth)
static async checkPrivyWalletExists(userId: string, options?: RequestInit) {
return fetch(this.triggerUrl(`/privy/wallets/${userId}/exists`), {
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async getPrivyWallets(userId: string, options?: RequestInit) {
return fetch(this.triggerUrl(`/privy/wallets/${userId}`), {
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async registerPrivyWallet(body: any, options?: RequestInit) {
return fetch(this.triggerUrl("/privy/wallets/register"), {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
})
.then((res) => res.json())
.then((data) => TriggerPriceOrderSchema.parse(data)); // adjust schema
}
// Price Trigger Orders
static async getPriceOrderById(orderId: string, options?: RequestInit) {
return fetch(this.triggerUrl(`/orders/price/${orderId}`), {
...this.getHeaders(),
...options,
})
.then((res) => res.json())
.then((data) => TriggerPriceOrderSchema.parse(data));
}
static async getPriceOrdersByUser(userAddress: string, options?: RequestInit) {
return fetch(this.triggerUrl(`/orders/price/by-user/${userAddress}`), {
...this.getHeaders(),
...options,
})
.then((res) => res.json())
.then((data) => z.array(TriggerPriceOrderSchema).parse(data));
}
static async getPriceOrderHistoryByUser(
userAddress: string,
params?: Record<string, string | number>,
options?: RequestInit,
) {
const url = new URL(this.triggerUrl(`/orders/history/by-user/${userAddress}`));
if (params) url.search = new URLSearchParams(params as any).toString();
return fetch(url, { ...this.getHeaders(), ...options })
.then((res) => res.json())
.then((data) => TriggerPriceOrderHistorySchema.array().parse(data));
}
static async createPriceOrder(body: any, options?: RequestInit) {
return fetch(this.triggerUrl("/orders/price"), {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
})
.then((res) => res.json())
.then((data) => TriggerPriceOrderSchema.parse(data));
}
static async updatePriceOrder(orderId: string, body: any, options?: RequestInit) {
return fetch(this.triggerUrl(`/orders/price/${orderId}`), {
method: "PATCH",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
})
.then((res) => res.json())
.then((data) => TriggerPriceOrderSchema.parse(data));
}
static async cancelPriceOrder(orderId: string, options?: RequestInit) {
return fetch(this.triggerUrl(`/orders/price/${orderId}/cancel`), {
method: "POST",
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async craftTransferTx(body: any, options?: RequestInit) {
return fetch(this.triggerUrl("/orders/price/craft-transfer"), {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async craftWithdrawalTx(orderId: string, options?: RequestInit) {
return fetch(this.triggerUrl(`/orders/price/${orderId}/craft-withdrawal`), {
method: "POST",
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async confirmWithdrawal(orderId: string, body: any, options?: RequestInit) {
return fetch(this.triggerUrl(`/orders/price/${orderId}/confirm-withdrawal`), {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
// Advanced Order Types
static async createOCOOrder(body: any, options?: RequestInit) {
return fetch(this.triggerUrl("/orders/price/oco"), {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async createOTOCOOrder(body: any, options?: RequestInit) {
return fetch(this.triggerUrl("/orders/price/otoco"), {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async cancelAllUserOrders(userAddress: string, options?: RequestInit) {
return fetch(this.triggerUrl(`/orders/price/cancel-all/${userAddress}`), {
method: "POST",
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async withdrawAllCancelled(userAddress: string, options?: RequestInit) {
return fetch(this.triggerUrl(`/orders/price/withdraw-all-cancelled/${userAddress}`), {
method: "POST",
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
static async confirmMultipleWithdrawals(body: any, options?: RequestInit) {
return fetch(this.triggerUrl("/orders/price/confirm-multiple-withdrawals"), {
method: "POST",
body: JSON.stringify(body),
...this.getHeaders(),
...options,
}).then((res) => res.json());
}
}
(async () => {
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY_BASE58!));
await JupiterLimitV2.authenticatePrivy(wallet);
const privyUser = await JupiterLimitV2.getPrivyWallets(wallet.publicKey.toBase58());
console.log(privyUser);
const inputAmount = "100000000"; // 0.01 SOL
const inputMint = "So11111111111111111111111111111111111111112"; // SOL
const outputMint = "Es9vMFrzaCER3H1WXy3PaY5E9sDcXavk6i7p2N6zk9W"; // USDT
// Step 1: Craft the transfer transaction (Jupiter gives you the unsigned tx)
const craftRes = await JupiterLimitV2.craftTransferTx({
senderAddress: wallet.publicKey.toBase58(),
receiverAddress: privyUser.privyWalletPubkey,
mint: inputMint,
amount: inputAmount,
prepareOrderAtas: { inputMint, outputMint },
});
console.log(craftRes);
// Step 2: Deserialize → Sign → Re-encode
const versionedTx = VersionedTransaction.deserialize(Buffer.from(craftRes.transaction, "base64"));
versionedTx.sign([wallet]);
const signedTransferTx = bs58.encode(versionedTx.serialize());
console.log(signedTransferTx);
// Step 3: Now create the actual OCO order
const ocoOrder = await JupiterLimitV2.createOCOOrder({
userPubkey: wallet.publicKey.toBase58(),
privyWalletId: privyUser.privyWalletId,
privyWalletPubkey: privyUser.privyWalletPubkey,
inputMint,
inputAmount,
outputMint,
triggerMint: inputMint, // usually SOL or USDC
aboveTriggerPriceUsd: "220", // Take profit
belowTriggerPriceUsd: "160", // Stop loss
expiresAt: Math.floor(Date.now() / 1000) + 30 * 24 * 3600, // 30 days
signedTransferTx, // This is now correctly formatted
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment