Creating an order in V2 is a two-step process: craft and sign a deposit transaction, then submit it alongside your order parameters.
For a working reference of the full order creation flow, see how jup.ag handles it. The frontend implements the same API with validation, error handling, and UX patterns you can use as a guide.
Prerequisites
Signing deposit transactions requires @solana/web3.js:
npm install @solana/web3.js
End-to-End Flow
1. Get vault → GET /v2/vault (or /v2/vault/register on first use)
2. Craft deposit → POST /v2/deposit/craft
3. Sign deposit tx → (client-side)
4. Create order → POST /v2/orders/price (with signed tx)
Step 1: Get Your Vault
Retrieve your vault, or register one if this is your first time. The vault is a Privy-managed custodial account that holds deposits for your orders.
let vaultResponse = await fetch('https://api.jup.ag/trigger/v2/vault', {
headers: {
'x-api-key': 'your-api-key',
'Authorization': `Bearer ${token}`,
},
});
// First time user — register a new vault
if (!vaultResponse.ok) {
vaultResponse = await fetch('https://api.jup.ag/trigger/v2/vault/register', {
headers: {
'x-api-key': 'your-api-key',
'Authorization': `Bearer ${token}`,
},
});
}
const vault = await vaultResponse.json();
// { userPubkey: "...", vaultPubkey: "...", privyVaultId: "..." }
Step 2: Craft a Deposit Transaction
The deposit endpoint builds an unsigned transaction that transfers tokens from your wallet to your vault.
const depositResponse = await fetch('https://api.jup.ag/trigger/v2/deposit/craft', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
inputMint: 'So11111111111111111111111111111111111111112', // SOL
outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
userAddress: walletAddress,
amount: '1000000000', // 1 SOL in lamports
}),
});
const deposit = await depositResponse.json();
Response:
{
"transaction": "Base64EncodedUnsignedTransaction...",
"requestId": "01234567-89ab-cdef-0123-456789abcdef",
"receiverAddress": "VaultPublicKey...",
"mint": "So11111111111111111111111111111111111111112",
"amount": "1000000000",
"tokenDecimals": 9
}
The vault address is automatically resolved from your JWT. You do not specify it in the request.
Step 3: Sign the Deposit Transaction
import { VersionedTransaction } from '@solana/web3.js';
const transaction = VersionedTransaction.deserialize(
Buffer.from(deposit.transaction, 'base64')
);
const signedTransaction = await wallet.signTransaction(transaction);
const depositSignedTx = Buffer.from(signedTransaction.serialize()).toString('base64');
Step 4: Create the Order
Submit the signed deposit alongside your order parameters. The order type determines which fields are required.
Single Order (Limit)
A single order triggers when the price of triggerMint crosses above or below the target price.
const orderResponse = await fetch('https://api.jup.ag/trigger/v2/orders/price', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
orderType: 'single',
depositRequestId: deposit.requestId,
depositSignedTx: depositSignedTx,
userPubkey: walletAddress,
inputMint: 'So11111111111111111111111111111111111111112',
inputAmount: '1000000000',
outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
triggerMint: 'So11111111111111111111111111111111111111112',
triggerCondition: 'above', // 'above' or 'below'
triggerPriceUsd: 200.00,
slippageBps: 100, // 1%
expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days
}),
});
const order = await orderResponse.json();
// { id: "order-uuid", txSignature: "..." }
OCO Order (One-Cancels-Other)
An OCO order creates a take-profit and stop-loss pair sharing one deposit. When one side fills, the other cancels automatically.
const orderResponse = await fetch('https://api.jup.ag/trigger/v2/orders/price', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
orderType: 'oco',
depositRequestId: deposit.requestId,
depositSignedTx: depositSignedTx,
userPubkey: walletAddress,
inputMint: 'So11111111111111111111111111111111111111112',
inputAmount: '1000000000',
outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
triggerMint: 'So11111111111111111111111111111111111111112',
tpPriceUsd: 250.00, // Take profit price
slPriceUsd: 150.00, // Stop loss price
tpSlippageBps: 100,
slSlippageBps: 100,
expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000,
}),
});
The take-profit price must be greater than the stop-loss price.
OTOCO Order (One-Triggers-One-Cancels-Other)
An OTOCO order has a parent trigger that executes first. Once filled, it automatically activates a TP/SL pair (OCO) on the output tokens.
const orderResponse = await fetch('https://api.jup.ag/trigger/v2/orders/price', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
orderType: 'otoco',
depositRequestId: deposit.requestId,
depositSignedTx: depositSignedTx,
userPubkey: walletAddress,
inputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
inputAmount: '200000000', // 200 USDC
outputMint: 'So11111111111111111111111111111111111111112',
triggerMint: 'So11111111111111111111111111111111111111112',
triggerCondition: 'below',
triggerPriceUsd: 180.00, // Entry: buy SOL when it drops to \$180
tpPriceUsd: 220.00, // Take profit at \$220
slPriceUsd: 160.00, // Stop loss at \$160
slippageBps: 100, // Parent order slippage
tpSlippageBps: 100,
slSlippageBps: 100,
expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
}),
});
Order Parameters Reference
Common Fields (All Order Types)
| Parameter | Type | Required | Description |
|---|
orderType | string | Yes | single, oco, or otoco |
depositRequestId | string | Yes | From the deposit craft response |
depositSignedTx | string | Yes | Base64-encoded signed deposit transaction |
userPubkey | string | Yes | Your wallet public key |
inputMint | string | Yes | Mint address of the token to sell |
inputAmount | string | Yes | Amount in smallest unit (lamports, etc.) |
outputMint | string | Yes | Mint address of the token to buy |
triggerMint | string | Yes | Mint address to monitor for price trigger |
expiresAt | number | Yes | Expiration timestamp in milliseconds |
Single Order Fields
| Parameter | Type | Required | Description |
|---|
triggerCondition | string | Yes | above or below |
triggerPriceUsd | number | Yes | USD price that triggers execution |
slippageBps | number | No | Slippage tolerance in basis points (0-10000) |
OCO Order Fields
| Parameter | Type | Required | Description |
|---|
tpPriceUsd | number | Yes | Take-profit trigger price (USD) |
slPriceUsd | number | Yes | Stop-loss trigger price (USD) |
tpSlippageBps | number | No | Take-profit slippage (basis points) |
slSlippageBps | number | No | Stop-loss slippage (basis points) |
OTOCO Order Fields
| Parameter | Type | Required | Description |
|---|
triggerCondition | string | Yes | Parent trigger: above or below |
triggerPriceUsd | number | Yes | Parent trigger price (USD) |
tpPriceUsd | number | Yes | Take-profit price after parent fills |
slPriceUsd | number | Yes | Stop-loss price after parent fills |
slippageBps | number | No | Parent order slippage |
tpSlippageBps | number | No | Take-profit slippage |
slSlippageBps | number | No | Stop-loss slippage |
Default Slippage
If slippage is not specified, defaults vary by order type and trigger condition:
| Order side | Default slippage |
|---|
| Take-profit / buy below (buying into strength) | Auto slippage via RTSE (Real-Time Slippage Estimator) |
| Stop-loss / buy above (selling into weakness) | 20% (2000 bps) |
Stop-loss orders use a higher default because execution certainty is more important than price precision when cutting losses.
Validation Rules
- Minimum order size: 10 USD
- Input and output mints must be different
- Slippage: 0-10000 basis points
expiresAt is required and must be a future timestamp (milliseconds)
- OCO/OTOCO: take-profit price must be greater than stop-loss price
Unlike V1 where orders could exist indefinitely, V2 requires an expiration on every order. Adding a TTL allows the system to prune orders that are unlikely to fill, improving execution quality for active orders. For long-lived orders, use a far-future timestamp (e.g. 30 days).
Response
{
"id": "order-uuid",
"txSignature": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d..."
}
The txSignature confirms the deposit transaction landed on-chain. The order is now active and will execute when price conditions are met.